mechanize 0.8.4 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mechanize might be problematic. Click here for more details.

Files changed (51) hide show
  1. data/EXAMPLES.txt +1 -1
  2. data/GUIDE.txt +11 -12
  3. data/History.txt +24 -0
  4. data/Manifest.txt +2 -1
  5. data/README.txt +1 -1
  6. data/Rakefile +2 -2
  7. data/lib/www/mechanize.rb +43 -11
  8. data/lib/www/mechanize/chain/header_resolver.rb +1 -1
  9. data/lib/www/mechanize/chain/request_resolver.rb +4 -0
  10. data/lib/www/mechanize/chain/response_body_parser.rb +1 -1
  11. data/lib/www/mechanize/chain/response_reader.rb +1 -1
  12. data/lib/www/mechanize/chain/ssl_resolver.rb +1 -1
  13. data/lib/www/mechanize/chain/uri_resolver.rb +21 -9
  14. data/lib/www/mechanize/cookie_jar.rb +11 -6
  15. data/lib/www/mechanize/file_response.rb +2 -0
  16. data/lib/www/mechanize/form.rb +33 -13
  17. data/lib/www/mechanize/form/multi_select_list.rb +2 -2
  18. data/lib/www/mechanize/form/radio_button.rb +1 -1
  19. data/lib/www/mechanize/list.rb +15 -38
  20. data/lib/www/mechanize/page.rb +17 -10
  21. data/lib/www/mechanize/page/link.rb +2 -2
  22. data/lib/www/mechanize/redirect_not_get_or_head_error.rb +20 -0
  23. data/test/helper.rb +4 -0
  24. data/test/htdocs/relative/tc_relative_links.html +1 -0
  25. data/test/servlets.rb +38 -8
  26. data/test/test_checkboxes.rb +14 -15
  27. data/test/test_cookie_class.rb +5 -4
  28. data/test/test_cookie_jar.rb +29 -0
  29. data/test/test_encoded_links.rb +1 -1
  30. data/test/test_errors.rb +1 -1
  31. data/test/test_follow_meta.rb +50 -6
  32. data/test/test_form_as_hash.rb +5 -5
  33. data/test/test_forms.rb +28 -27
  34. data/test/test_links.rb +22 -16
  35. data/test/test_mech.rb +24 -7
  36. data/test/test_multi_select.rb +27 -27
  37. data/test/test_pluggable_parser.rb +4 -4
  38. data/test/test_radiobutton.rb +35 -42
  39. data/test/test_redirect_verb_handling.rb +45 -0
  40. data/test/test_referer.rb +1 -1
  41. data/test/test_relative_links.rb +4 -4
  42. data/test/test_scheme.rb +4 -0
  43. data/test/test_select.rb +15 -15
  44. data/test/test_select_all.rb +1 -1
  45. data/test/test_select_none.rb +1 -1
  46. data/test/test_select_noopts.rb +1 -1
  47. data/test/test_set_fields.rb +4 -4
  48. data/test/test_textarea.rb +13 -13
  49. data/test/test_upload.rb +1 -1
  50. metadata +10 -6
  51. data/NOTES.txt +0 -318
data/EXAMPLES.txt CHANGED
@@ -117,7 +117,7 @@ This example also demonstrates subclassing Mechanize.
117
117
  search_form.words = 'WWW'
118
118
  submit search_form
119
119
 
120
- page.links.with.href( %r{/projects/} ).each do |link|
120
+ page.links_with(:href => %r{/projects/} ).each do |link|
121
121
  next if link.href =~ %r{/projects/support/}
122
122
 
123
123
  puts 'Loading %-30s %s' % [link.href, link.text]
data/GUIDE.txt CHANGED
@@ -36,17 +36,16 @@ link to click on. Lets say we wanted to click the link whose text is 'News'.
36
36
  Normally, we would have to do this:
37
37
  page = agent.click page.links.find { |l| l.text == 'News' }
38
38
  But Mechanize gives us a shortcut. Instead we can say this:
39
- page = agent.click page.links.text('News')
39
+ page = agent.click page.link_with(:text => 'News')
40
40
  That shortcut says "find all links with the name 'News'". You're probably
41
41
  thinking "there could be multiple links with that text!", and you would be
42
- correct! If you pass a list of links to the "click" method, Mechanize will
43
- click on the first one. If you wanted to click on the second news link, you
44
- could do this:
45
- agent.click page.links.text('News')[1]
42
+ correct! If you use the plural form, you can access the list.
43
+ If you wanted to click on the second news link, you could do this:
44
+ agent.click page.links_with(:text => 'News')[1]
46
45
  We can even find a link with a certain href like so:
47
- page.links.href('/something')
46
+ page.links_with(:href => '/something')
48
47
  Or chain them together to find a link with certain text and certain href:
49
- page.links.text('News').href('/something')
48
+ page.links_with(:text => 'News', :href => '/something')
50
49
 
51
50
  These shortcuts that mechanize provides are available on any list that you
52
51
  can fetch like frames, iframes, or forms. Now that we know how to find and
@@ -102,24 +101,24 @@ have many options associated with them. If you select one option, mechanize
102
101
  will deselect the other options (unless it is a multi select!).
103
102
 
104
103
  For example, lets select an option on a list:
105
- form.fields.name('list').options[0].select
104
+ form.field_with(:name => 'list').options[0].select
106
105
 
107
106
  Now lets take a look at checkboxes and radio buttons. To select a checkbox,
108
107
  just check it like this:
109
- form.checkboxes.name('box').check
108
+ form.checkbox_with(:name => 'box').check
110
109
  Radio buttons are very similar to checkboxes, but they know how to uncheck
111
110
  other radio buttons of the same name. Just check a radio button like you
112
111
  would a checkbox:
113
- form.radiobuttons.name('box')[1].check
112
+ form.radiobuttons_with(:name => 'box')[1].check
114
113
  Mechanize also makes file uploads easy! Just find the file upload field, and
115
114
  tell it what file name you want to upload:
116
- form.file_uploads.file_name = "somefile.jpg"
115
+ form.file_uploads.first.file_name = "somefile.jpg"
117
116
 
118
117
  == Scraping Data
119
118
  Mechanize uses hpricot[http://code.whytheluckystiff.net/hpricot/] to parse
120
119
  html. What does this mean for you? You can treat a mechanize page like
121
120
  an hpricot object. After you have used Mechanize to navigate to the page
122
121
  that you need to scrape, then scrape it using hpricot methods:
123
- agent.get('http://someurl.com/').search("//p[@class='posted']")
122
+ agent.get('http://someurl.com/').search(".//p[@class='posted']")
124
123
  For more information on this powerful scraper, take a look at
125
124
  HpricotBasics[http://code.whytheluckystiff.net/hpricot/wiki/HpricotBasics]
data/History.txt CHANGED
@@ -1,5 +1,29 @@
1
1
  = Mechanize CHANGELOG
2
2
 
3
+ === 0.8.5
4
+
5
+ * Deprecations
6
+ * WWW::Mechanize::List will be deprecated in 0.9.0, and warnings have
7
+ been added to help you upgrade.
8
+
9
+ * Bug Fixes:
10
+ * Stopped raising EOF exceptions on HEAD requests. ありがとう:HIRAKU Kuroda
11
+ * Fixed exceptions when a logger is set and file:// requests are made.
12
+ * Made Mechanize 1.9 compatible
13
+ * Not setting the port in the host header for SSL sites.
14
+ * Following refresh headers. Thanks Tim Connor!
15
+ * Cookie Jar handles cookie domains containing ports, like
16
+ 'mydomain.com:443' (Thanks Michal Ochman!)
17
+ * Fixing strange uri escaping problems [#22604]
18
+ * Making content-type determintation more robust. (thanks Han Holl!)
19
+ * Dealing with links that are query string only. [#22402]
20
+ * Nokogiri may be dropped in as a replacement.
21
+ WWW::Mechanize.html_parser = Nokogiri::HTML
22
+ * Making sure the correct page is added to the history on meta refresh.
23
+ [#22708]
24
+ * Mechanize#get requests no longer send a referer unless they are relative
25
+ requests.
26
+
3
27
  === 0.8.4
4
28
 
5
29
  * Bug Fixes:
data/Manifest.txt CHANGED
@@ -4,7 +4,6 @@ GUIDE.txt
4
4
  History.txt
5
5
  LICENSE.txt
6
6
  Manifest.txt
7
- NOTES.txt
8
7
  README.txt
9
8
  Rakefile
10
9
  examples/flickr_upload.rb
@@ -58,6 +57,7 @@ lib/www/mechanize/page/link.rb
58
57
  lib/www/mechanize/page/meta.rb
59
58
  lib/www/mechanize/pluggable_parsers.rb
60
59
  lib/www/mechanize/redirect_limit_reached_error.rb
60
+ lib/www/mechanize/redirect_not_get_or_head_error.rb
61
61
  lib/www/mechanize/response_code_error.rb
62
62
  lib/www/mechanize/unsupported_scheme_error.rb
63
63
  lib/www/mechanize/util.rb
@@ -151,6 +151,7 @@ test/test_post_form.rb
151
151
  test/test_pretty_print.rb
152
152
  test/test_radiobutton.rb
153
153
  test/test_redirect_limit_reached.rb
154
+ test/test_redirect_verb_handling.rb
154
155
  test/test_referer.rb
155
156
  test/test_relative_links.rb
156
157
  test/test_response_code.rb
data/README.txt CHANGED
@@ -29,7 +29,7 @@ Copyright (c) 2005 by Michael Neumann (mneumann@ntecs.de)
29
29
  Copyright (c) 2006-2008:
30
30
 
31
31
  * {Aaron Patterson}[http://tenderlovemaking.com] (aaronp@rubyforge.org)
32
- * Mike Dalessio (mike@csa.net)
32
+ * {Mike Dalessio}[http://mike.daless.io] (mike@csa.net)
33
33
 
34
34
  This library comes with a shameless plug for employing me
35
35
  (Aaron[http://tenderlovemaking.com/]) programming
data/Rakefile CHANGED
@@ -6,8 +6,8 @@ require 'mechanize'
6
6
 
7
7
  HOE = Hoe.new('mechanize', WWW::Mechanize::VERSION) do |p|
8
8
  p.rubyforge_name = 'mechanize'
9
- p.author = 'Aaron Patterson'
10
- p.email = 'aaronp@rubyforge.org'
9
+ p.developer('Aaron Patterson','aaronp@rubyforge.org')
10
+ p.developer('Mike Dalessio','mike.dalessio@gmail.com')
11
11
  p.summary = "Mechanize provides automated web-browsing"
12
12
  p.extra_deps = [['hpricot', '>= 0.5.0']]
13
13
  end
data/lib/www/mechanize.rb CHANGED
@@ -14,6 +14,7 @@ require 'www/mechanize/content_type_error'
14
14
  require 'www/mechanize/response_code_error'
15
15
  require 'www/mechanize/unsupported_scheme_error'
16
16
  require 'www/mechanize/redirect_limit_reached_error'
17
+ require 'www/mechanize/redirect_not_get_or_head_error'
17
18
  require 'www/mechanize/cookie'
18
19
  require 'www/mechanize/cookie_jar'
19
20
  require 'www/mechanize/history'
@@ -39,14 +40,14 @@ module WWW
39
40
  # agent = WWW::Mechanize.new { |a| a.log = Logger.new("mech.log") }
40
41
  # agent.user_agent_alias = 'Mac Safari'
41
42
  # page = agent.get("http://www.google.com/")
42
- # search_form = page.forms.name("f").first
43
- # search_form.fields.name("q").value = "Hello"
43
+ # search_form = page.form_with(:name => "f")
44
+ # search_form.field_with(:name => "q").value = "Hello"
44
45
  # search_results = agent.submit(search_form)
45
46
  # puts search_results.body
46
47
  class Mechanize
47
48
  ##
48
49
  # The version of Mechanize you are using.
49
- VERSION = '0.8.4'
50
+ VERSION = '0.8.5'
50
51
 
51
52
  ##
52
53
  # User Agent aliases
@@ -203,7 +204,13 @@ module WWW
203
204
  headers = options[:headers]
204
205
  end
205
206
 
206
- referer ||= current_page || Page.new(nil, {'content-type'=>'text/html'})
207
+ unless referer
208
+ if url =~ /^http/
209
+ referer = Page.new(nil, {'content-type'=>'text/html'})
210
+ else
211
+ referer = current_page || Page.new(nil, {'content-type'=>'text/html'})
212
+ end
213
+ end
207
214
 
208
215
  # FIXME: Huge hack so that using a URI as a referer works. I need to
209
216
  # refactor everything to pass around URIs but still support
@@ -440,8 +447,8 @@ module WWW
440
447
  http_obj = options[:connection]
441
448
 
442
449
  # Add If-Modified-Since if page is in history
443
- if( (page = visited_page(uri)) && cur_page.response['Last-Modified'] )
444
- request['If-Modified-Since'] = cur_page.response['Last-Modified']
450
+ if( (page = visited_page(uri)) && page.response['Last-Modified'] )
451
+ request['If-Modified-Since'] = page.response['Last-Modified']
445
452
  end if(@conditional_requests)
446
453
 
447
454
  # Specify timeouts if given
@@ -458,9 +465,9 @@ module WWW
458
465
  # Send the request
459
466
  attempts = 0
460
467
  begin
461
- response = http_obj.request(request, *request_data) { |response|
468
+ response = http_obj.request(request, *request_data) { |r|
462
469
  connection_chain = Chain.new([
463
- Chain::ResponseReader.new(response),
470
+ Chain::ResponseReader.new(r),
464
471
  Chain::BodyDecodingHandler.new,
465
472
  ])
466
473
  connection_chain.handle(options)
@@ -488,9 +495,32 @@ module WWW
488
495
 
489
496
  log.info("status: #{ page.code }") if log
490
497
 
491
- if follow_meta_refresh && page.respond_to?(:meta) &&
492
- (redirect = page.meta.first)
493
- return redirect.click
498
+ if follow_meta_refresh
499
+ redirect_uri = nil
500
+ if (page.respond_to?(:meta) && (redirect = page.meta.first))
501
+ redirect_uri = redirect.uri.to_s
502
+ elsif refresh = response['refresh']
503
+ parsed_refresh = refresh.match(/^\s*(\d+\.?\d*);\s*(url|URL)=(\S*)\s*$/)
504
+ raise StandardError, "Invalid refresh http header" unless parsed_refresh
505
+ delay = parsed_refresh[1]
506
+ location = parsed_refresh[3]
507
+ location = "http://#{uri.host}#{location}" unless location.include?("http")
508
+ if redirects + 1 > redirection_limit
509
+ raise RedirectLimitReachedError.new(page, redirects)
510
+ end
511
+ sleep delay.to_i
512
+ redirect_uri = location
513
+ end
514
+ if redirect_uri
515
+ @history.push(page, page.uri)
516
+ return fetch_page(
517
+ :uri => redirect_uri,
518
+ :referer => page,
519
+ :params => [],
520
+ :verb => :get,
521
+ :redirects => redirects + 1
522
+ )
523
+ end
494
524
  end
495
525
 
496
526
  return page if res_klass <= Net::HTTPSuccess
@@ -503,9 +533,11 @@ module WWW
503
533
  log.info("follow redirect to: #{ response['Location'] }") if log
504
534
  from_uri = page.uri
505
535
  raise RedirectLimitReachedError.new(page, redirects) if redirects + 1 > redirection_limit
536
+ redirect_verb = options[:verb] == :head ? :head : :get
506
537
  page = fetch_page( :uri => response['Location'].to_s,
507
538
  :referer => page,
508
539
  :params => [],
540
+ :verb => redirect_verb,
509
541
  :redirects => redirects + 1
510
542
  )
511
543
  @history.push(page, from_uri)
@@ -23,7 +23,7 @@ module WWW
23
23
  end
24
24
  request['Accept-Encoding'] = 'gzip,identity'
25
25
  request['Accept-Language'] = 'en-us,en;q=0.5'
26
- host = "#{uri.host}#{uri.port.to_i == 80 ? '' : ':' + uri.port.to_s}"
26
+ host = "#{uri.host}#{[80, 443].include?(uri.port.to_i) ? '' : ':' + uri.port.to_s}"
27
27
  request['Host'] = host
28
28
  request['Accept-Charset'] = 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'
29
29
 
@@ -16,6 +16,10 @@ module WWW
16
16
  class << o
17
17
  def add_field(*args); end
18
18
  alias :[]= :add_field
19
+ def path
20
+ uri.path
21
+ end
22
+ def each_header; end
19
23
  end
20
24
  params[:request] ||= o
21
25
  end
@@ -17,7 +17,7 @@ module WWW
17
17
  content_type = nil
18
18
  unless response['Content-Type'].nil?
19
19
  data = response['Content-Type'].match(/^([^;]*)/)
20
- content_type = data[1].downcase unless data.nil?
20
+ content_type = data[1].downcase.split(',')[0] unless data.nil?
21
21
  end
22
22
 
23
23
  # Find our pluggable parser
@@ -24,7 +24,7 @@ module WWW
24
24
 
25
25
  # Net::HTTP ignores EOFError if Content-length is given, so we emulate it here.
26
26
  unless res_klass <= Net::HTTPRedirection
27
- raise EOFError if @response.content_length() && @response.content_length() != total
27
+ raise EOFError if (!params[:request].is_a?(Net::HTTP::Head)) && @response.content_length() && @response.content_length() != total
28
28
  end
29
29
 
30
30
  @response.each_header { |k,v|
@@ -15,7 +15,7 @@ module WWW
15
15
  def handle(ctx, params)
16
16
  uri = params[:uri]
17
17
  http_obj = params[:connection]
18
- if uri.scheme == 'https' && ! http_obj.started?
18
+ if uri.scheme == 'https' && ! http_obj.started? && ! http_obj.frozen?
19
19
  http_obj.use_ssl = true
20
20
  http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE
21
21
  if @ca_file
@@ -17,19 +17,31 @@ module WWW
17
17
  sprintf('%%%X', match.unpack($KCODE == 'UTF8' ? 'U' : 'c')[0])
18
18
  }
19
19
 
20
- uri = URI.parse(
21
- Util.html_unescape(
22
- uri.split(/(?:%[0-9A-Fa-f]{2})+|#/).zip(
23
- uri.scan(/(?:%[0-9A-Fa-f]{2})+|#/)
24
- ).map { |x,y|
25
- "#{URI.escape(x)}#{y}"
26
- }.join('')
27
- )
28
- )
20
+ escaped_uri = Util.html_unescape(
21
+ uri.split(/(?:%[0-9A-Fa-f]{2})+|#/).zip(
22
+ uri.scan(/(?:%[0-9A-Fa-f]{2})+|#/)
23
+ ).map { |x,y|
24
+ "#{URI.escape(x)}#{y}"
25
+ }.join('')
26
+ )
27
+
28
+ begin
29
+ uri = URI.parse(escaped_uri)
30
+ rescue
31
+ uri = URI.parse(URI.escape(escaped_uri))
32
+ end
33
+
29
34
  end
30
35
  uri = @scheme_handlers[
31
36
  uri.relative? ? 'relative' : uri.scheme.downcase
32
37
  ].call(uri, params[:referer])
38
+
39
+ if params[:referer] && params[:referer].uri
40
+ if uri.path.length == 0 && uri.relative?
41
+ uri.path = params[:referer].uri.path
42
+ end
43
+ end
44
+
33
45
  uri.path = '/' if uri.path.length == 0
34
46
 
35
47
  if uri.relative?
@@ -13,7 +13,7 @@ module WWW
13
13
 
14
14
  # Add a cookie to the Jar.
15
15
  def add(uri, cookie)
16
- return unless uri.host =~ /#{cookie.domain}$/i
16
+ return unless uri.host =~ /#{CookieJar.strip_port(cookie.domain)}$/i
17
17
  normal_domain = cookie.domain.downcase
18
18
  unless @jar.has_key?(normal_domain)
19
19
  @jar[normal_domain] = Hash.new
@@ -30,7 +30,7 @@ module WWW
30
30
  cookies = []
31
31
  url.path = '/' if url.path.empty?
32
32
  @jar.each_key do |domain|
33
- if url.host =~ /#{domain}$/i
33
+ if url.host =~ /#{CookieJar.strip_port(domain)}$/i
34
34
  @jar[domain].each_key do |name|
35
35
  if url.path =~ /^#{@jar[domain][name].path}/
36
36
  if @jar[domain][name].expires.nil?
@@ -68,9 +68,9 @@ module WWW
68
68
  def save_as(file, format = :yaml)
69
69
  ::File.open(file, "w") { |f|
70
70
  case format
71
- when :yaml:
71
+ when :yaml then
72
72
  YAML::dump(@jar, f)
73
- when :cookiestxt:
73
+ when :cookiestxt then
74
74
  dump_cookiestxt(f)
75
75
  else
76
76
  raise "Unknown cookie jar file format"
@@ -86,9 +86,9 @@ module WWW
86
86
  def load(file, format = :yaml)
87
87
  @jar = ::File.open(file) { |f|
88
88
  case format
89
- when :yaml:
89
+ when :yaml then
90
90
  YAML::load(f)
91
- when :cookiestxt:
91
+ when :cookiestxt then
92
92
  load_cookiestxt(f)
93
93
  else
94
94
  raise "Unknown cookie jar file format"
@@ -181,6 +181,11 @@ module WWW
181
181
  end
182
182
  end
183
183
  end
184
+
185
+ def self.strip_port(host)
186
+ host.gsub(/:[0-9]+$/,'')
187
+ end
188
+
184
189
  end
185
190
  end
186
191
  end
@@ -28,6 +28,8 @@ module WWW
28
28
  ::File.exists?(@file_path) ? ::File.stat(@file_path).size : 0
29
29
  end
30
30
 
31
+ def each_header; end
32
+
31
33
  def [](key)
32
34
  return nil unless key.downcase == 'content-type'
33
35
  return 'text/html' if directory?
@@ -61,11 +61,6 @@ module WWW
61
61
 
62
62
  def values; fields.map { |f| f.value }; end
63
63
 
64
- # Fetch the first field whose name is equal to +field_name+
65
- def field(field_name)
66
- fields.find { |f| f.name.eql? field_name }
67
- end
68
-
69
64
  # Add a field with +field_name+ and +value+
70
65
  def add_field!(field_name, value = nil)
71
66
  fields << Field.new(field_name, value)
@@ -84,16 +79,16 @@ module WWW
84
79
  case v
85
80
  when Hash
86
81
  v.each do |index, value|
87
- self.fields.name(k.to_s).[](index).value = value
82
+ self.fields_with(:name => k.to_s).[](index).value = value
88
83
  end
89
84
  else
90
85
  value = nil
91
86
  index = 0
92
- v.each do |val|
87
+ [v].flatten.each do |val|
93
88
  index = val.to_i unless value.nil?
94
89
  value = val if value.nil?
95
90
  end
96
- self.fields.name(k.to_s).[](index).value = value
91
+ self.fields_with(:name => k.to_s).[](index).value = value
97
92
  end
98
93
  end
99
94
  end
@@ -209,7 +204,32 @@ module WWW
209
204
  def delete_field!(field_name)
210
205
  @fields.delete_if{ |f| f.name == field_name}
211
206
  end
212
-
207
+
208
+ { :field => :fields,
209
+ :button => :buttons,
210
+ :file_upload => :file_uploads,
211
+ :radiobutton => :radiobuttons,
212
+ :checkbox => :checkboxes,
213
+ }.each do |singular,plural|
214
+ eval(<<-eomethod)
215
+ def #{plural}_with criteria = {}
216
+ criteria = {:name => criteria} if String === criteria
217
+ f = #{plural}.find_all do |thing|
218
+ criteria.all? { |k,v| v === thing.send(k) }
219
+ end
220
+ yield f if block_given?
221
+ f
222
+ end
223
+
224
+ def #{singular}_with criteria = {}
225
+ f = #{plural}_with(criteria).first
226
+ yield f if block_given?
227
+ f
228
+ end
229
+ alias :#{singular} :#{singular}_with
230
+ eomethod
231
+ end
232
+
213
233
  private
214
234
  def parse
215
235
  @fields = WWW::Mechanize::List.new
@@ -219,7 +239,7 @@ module WWW
219
239
  @checkboxes = WWW::Mechanize::List.new
220
240
 
221
241
  # Find all input tags
222
- form_node.search('//input').each do |node|
242
+ form_node.search('input').each do |node|
223
243
  type = (node['type'] || 'text').downcase
224
244
  name = node['name']
225
245
  next if name.nil? && !(type == 'submit' || type =='button')
@@ -242,13 +262,13 @@ module WWW
242
262
  end
243
263
 
244
264
  # Find all textarea tags
245
- form_node.search('//textarea').each do |node|
265
+ form_node.search('textarea').each do |node|
246
266
  next if node['name'].nil?
247
267
  @fields << Field.new(node['name'], node.inner_text)
248
268
  end
249
269
 
250
270
  # Find all select tags
251
- form_node.search('//select').each do |node|
271
+ form_node.search('select').each do |node|
252
272
  next if node['name'].nil?
253
273
  if node.has_attribute? 'multiple'
254
274
  @fields << MultiSelectList.new(node['name'], node)
@@ -259,7 +279,7 @@ module WWW
259
279
 
260
280
  # Find all submit button tags
261
281
  # FIXME: what can I do with the reset buttons?
262
- form_node.search('//button').each do |node|
282
+ form_node.search('button').each do |node|
263
283
  type = (node['type'] || 'submit').downcase
264
284
  next if type == 'reset'
265
285
  @buttons << Button.new(node['name'], node['value'])