qa_robusta 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/.autotest +23 -0
  2. data/.gemtest +0 -0
  3. data/History.txt +6 -0
  4. data/Manifest.txt +101 -0
  5. data/README.txt +48 -0
  6. data/Rakefile +18 -0
  7. data/bin/qa_robusta +14 -0
  8. data/common/Rakefile +95 -0
  9. data/common/conf/monkey_patch.yaml +8 -0
  10. data/common/conf/monkey_patch.yaml.ex +10 -0
  11. data/common/lib/array.rb +9 -0
  12. data/common/lib/cache.rb +12 -0
  13. data/common/lib/constants.rb +35 -0
  14. data/common/lib/error_defns.rb +13 -0
  15. data/common/lib/format_html_tmp.html +34 -0
  16. data/common/lib/formatters.rb +18 -0
  17. data/common/lib/gem_helpers.rb +29 -0
  18. data/common/lib/gems/.svn/entries +43 -0
  19. data/common/lib/gems/cache/.svn/entries +28 -0
  20. data/common/lib/gems/doc/.svn/entries +28 -0
  21. data/common/lib/gems/gems/.svn/entries +28 -0
  22. data/common/lib/gems/installed/.svn/entries +40 -0
  23. data/common/lib/gems/installed/cache/.svn/entries +28 -0
  24. data/common/lib/gems/installed/doc/.svn/entries +28 -0
  25. data/common/lib/gems/installed/gems/.svn/entries +28 -0
  26. data/common/lib/gems/installed/specifications/.svn/entries +28 -0
  27. data/common/lib/gems/specifications/.svn/entries +28 -0
  28. data/common/lib/gen_suite_doc.rb +52 -0
  29. data/common/lib/load_test_data.rb +18 -0
  30. data/common/lib/monkey_patch.rb +149 -0
  31. data/common/lib/navigate_mech.rb +79 -0
  32. data/common/lib/update_element.rb +12 -0
  33. data/common/monkey_patches/mechanize.rb +589 -0
  34. data/common/monkey_patches/table.rb +363 -0
  35. data/common/monkey_patches/telnet.rb +420 -0
  36. data/common/monkey_patches/unit.rb +538 -0
  37. data/demo/demo_site.rb +38 -0
  38. data/demo/public/javascripts/jquery-1.6.2.min.js +18 -0
  39. data/demo/views/index.erb +35 -0
  40. data/lib/monkey_patch.rb +26 -0
  41. data/lib/qa_robusta.rb +3 -0
  42. data/mechanize_interface/conf/app.yaml +10 -0
  43. data/mechanize_interface/lib/agent.rb +18 -0
  44. data/mechanize_interface/lib/app_require.rb +19 -0
  45. data/mechanize_interface/lib/get_page.rb +20 -0
  46. data/mechanize_interface/lib/login.rb +30 -0
  47. data/mechanize_interface/lib/navigation_paths.rb +12 -0
  48. data/mechanize_interface/test/lib/mech_unit_test.rb +19 -0
  49. data/qa_observer/conf/dev_users.yaml +9 -0
  50. data/qa_observer/conf/development.yaml +3 -0
  51. data/qa_observer/conf/qa_observer_links.yaml +10 -0
  52. data/qa_observer/generators/site/site_generator.rb +61 -0
  53. data/qa_observer/generators/site/templates/base_urls.yaml.erb +6 -0
  54. data/qa_observer/generators/site/templates/create_flow.rb.erb +54 -0
  55. data/qa_observer/generators/site/templates/elements.rb.erb +61 -0
  56. data/qa_observer/generators/site/templates/html_elements.yaml +22 -0
  57. data/qa_observer/generators/site/templates/navigate.rb +32 -0
  58. data/qa_observer/generators/site/templates/reports.rb +7 -0
  59. data/qa_observer/generators/site/templates/users_list.yaml +21 -0
  60. data/qa_observer/generators/test_case/templates/test_case.rb.erb +62 -0
  61. data/qa_observer/generators/test_case/test_case_generator.rb +29 -0
  62. data/qa_observer/lib/doc.rb +103 -0
  63. data/qa_observer/lib/env_details.rb +2 -0
  64. data/qa_observer/lib/error_defns.rb +3 -0
  65. data/qa_observer/lib/form_helpers.rb +52 -0
  66. data/qa_observer/lib/reports.rb +7 -0
  67. data/qa_observer/lib/requires.rb +23 -0
  68. data/qa_observer/lib/system_test.rb +146 -0
  69. data/qa_observer/lib/test_extention.rb +29 -0
  70. data/qa_observer/lib/update_element.rb +12 -0
  71. data/qa_observer/lib/watir.rb +42 -0
  72. data/qa_observer/script/destroy +14 -0
  73. data/qa_observer/script/generate +14 -0
  74. data/qa_observer/sites/demo/conf/base_urls.yaml +6 -0
  75. data/qa_observer/sites/demo/conf/html_elements.yaml +22 -0
  76. data/qa_observer/sites/demo/conf/remote_machine.yaml +12 -0
  77. data/qa_observer/sites/demo/conf/users_list.yaml +21 -0
  78. data/qa_observer/sites/demo/elements/demo_elements.rb +9 -0
  79. data/qa_observer/sites/demo/flows/demo_flows.rb +37 -0
  80. data/qa_observer/sites/demo/helpers/.placeholder +0 -0
  81. data/qa_observer/sites/demo/lib/navigate.rb +32 -0
  82. data/qa_observer/sites/demo/lib/reports.rb +7 -0
  83. data/qa_observer/sites/demo/results/.placeholder +0 -0
  84. data/qa_observer/sites/demo/results/images/.placeholder +0 -0
  85. data/qa_observer/sites/demo/test_cases/home_page.rb +106 -0
  86. data/qa_observer/suites/demo_lg_chrome/lg.yaml +11 -0
  87. data/qa_observer/suites/demo_md_chrome/md.yaml +11 -0
  88. data/qa_observer/suites/demo_sm_chrome/sm.yaml +11 -0
  89. data/qa_observer/suites/init.rb +41 -0
  90. data/qa_observer/test_runner.rb +125 -0
  91. data/remote_unix/helpers/constants.rb +8 -0
  92. data/remote_unix/helpers/out_xforms.rb +48 -0
  93. data/remote_unix/lib/common.rb +52 -0
  94. data/remote_unix/lib/error_defn.rb +4 -0
  95. data/remote_unix/lib/general_unix.rb +308 -0
  96. data/remote_unix/lib/network_commands.rb +41 -0
  97. data/remote_unix/lib/network_info.rb +98 -0
  98. data/remote_unix/lib/postfix_commands.rb +87 -0
  99. data/remote_unix/lib/postfix_info.rb +30 -0
  100. data/remote_unix/lib/requires.rb +25 -0
  101. data/remote_unix/lib/scp.rb +22 -0
  102. data/remote_unix/lib/ssh.rb +46 -0
  103. data/test/test_qa_robusta.rb +8 -0
  104. metadata +219 -0
@@ -0,0 +1,79 @@
1
+ require 'yaml'
2
+ base=File.expand_path(File.dirname(__FILE__))
3
+ require "#{base}/gem_helpers"
4
+ GemHelpers.update_gem_path "#{base}/../gems/installed"
5
+ require 'mechanize'
6
+ #=begin
7
+
8
+ module MechanizeHelpers
9
+ def click_link(params={})
10
+ # default :how to :href if not passed in
11
+ params[:how] ||= :href
12
+ raise ArgumentError, "hash key :what not provided" unless params.has_key?(:what) && params.has_key?(:page)
13
+ what_escape = Regexp.escape(params[:what])
14
+ params[:page].links.each { |link|
15
+ if params[:how] == :href
16
+ if link.href =~ /#{what_escape}/
17
+ puts "FOUND LINK: #{link.inspect}"
18
+ page = link.click
19
+ break
20
+ end
21
+ end
22
+ }
23
+ puts page.inspect
24
+ page
25
+ end
26
+
27
+ end
28
+
29
+ #TODO: tie into mechanize core
30
+ module WWW
31
+ class Mechanize
32
+ def find_link(params={:links => [], :how => nil, :what => nil})
33
+ params[:links].each { |l|
34
+ return l if eval("l.#{params[:how]} =~ /#{params[:what]}/im")
35
+ }
36
+ # ensure if we get here not to return the last link
37
+ nil
38
+ end
39
+
40
+ def find_form(page, element)
41
+ types=['checkboxes', 'radiobuttons']
42
+ page.forms.each { |f|
43
+ if f.elements.inspect.to_s =~ /#{element}/
44
+ return f
45
+ else
46
+ types.each { |type|
47
+ has_elem=eval("f.#{type}.inspect.to_s")
48
+ return f if has_elem.to_s =~ /#{element}/
49
+ }
50
+ end
51
+ }
52
+ end
53
+
54
+ def go(cfg, page, navigation_path=[])
55
+ navigation_path.each { |i|
56
+ if i.has_key?(:submit)
57
+ page = @form.submit
58
+ else
59
+ if cfg[i[:page]][i[:link]].has_key?(:form)
60
+ # if we have the key and no value find form
61
+ if cfg[i[:page]][i[:link]][:form] == "" ||
62
+ cfg[i[:page]][i[:link]][:form] == nil
63
+ @form = find_form(page, cfg[i[:page]][i[:link]][:what])
64
+ else
65
+ forms=page.forms
66
+ @form = eval("forms.#{cfg[i[:page]][i[:link]][:form]}")
67
+ end
68
+ @form[cfg[i[:page]][i[:link]][:what]] = i[:value]
69
+ else
70
+ l = find_link(cfg[i[:page]][i[:link]].merge(:links => page.links))
71
+ page=eval("l.#{cfg[i[:page]][i[:link]][:action]}")
72
+ end
73
+ end
74
+ }
75
+
76
+ page
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,12 @@
1
+ base = File.expand_path(File.dirname(__FILE__))
2
+
3
+ Dir.open("#{base}/firewatir").each { |i|
4
+ file = "#{base}/firewatir/#{i}"
5
+ next if File.directory?(file)
6
+ f = File.read(file)
7
+ f = f.gsub("< Element", "< Tmp::Element")
8
+ fd=File.open(file, 'w+')
9
+ fd.puts f
10
+ fd.close
11
+ }
12
+
@@ -0,0 +1,589 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'webrick/httputils'
5
+ require 'zlib'
6
+ require 'stringio'
7
+ require 'digest/md5'
8
+ require 'fileutils'
9
+ require 'nokogiri'
10
+ require 'forwardable'
11
+ require 'iconv'
12
+ require 'nkf'
13
+
14
+ require 'www/mechanize/util'
15
+ require 'www/mechanize/content_type_error'
16
+ require 'www/mechanize/response_code_error'
17
+ require 'www/mechanize/unsupported_scheme_error'
18
+ require 'www/mechanize/redirect_limit_reached_error'
19
+ require 'www/mechanize/redirect_not_get_or_head_error'
20
+ require 'www/mechanize/cookie'
21
+ require 'www/mechanize/cookie_jar'
22
+ require 'www/mechanize/history'
23
+ require 'www/mechanize/form'
24
+ require 'www/mechanize/pluggable_parsers'
25
+ require 'www/mechanize/file_response'
26
+ require 'www/mechanize/inspect'
27
+ require 'www/mechanize/chain'
28
+ require 'www/mechanize/monkey_patch'
29
+
30
+ module WWW
31
+ # = Synopsis
32
+ # The Mechanize library is used for automating interaction with a website. It
33
+ # can follow links, and submit forms. Form fields can be populated and
34
+ # submitted. A history of URL's is maintained and can be queried.
35
+ #
36
+ # == Example
37
+ # require 'rubygems'
38
+ # require 'mechanize'
39
+ # require 'logger'
40
+ #
41
+ # agent = WWW::Mechanize.new { |a| a.log = Logger.new("mech.log") }
42
+ # agent.user_agent_alias = 'Mac Safari'
43
+ # page = agent.get("http://www.google.com/")
44
+ # search_form = page.form_with(:name => "f")
45
+ # search_form.field_with(:name => "q").value = "Hello"
46
+ # search_results = agent.submit(search_form)
47
+ # puts search_results.body
48
+ class Mechanize
49
+ ##
50
+ # The version of Mechanize you are using.
51
+ VERSION = '0.9.3'
52
+
53
+ ##
54
+ # User Agent aliases
55
+ AGENT_ALIASES = {
56
+ 'Windows IE 6' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
57
+ 'Windows IE 7' => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
58
+ 'Windows Mozilla' => 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6',
59
+ 'Mac Safari' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3',
60
+ 'Mac FireFox' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3',
61
+ 'Mac Mozilla' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4a) Gecko/20030401',
62
+ 'Linux Mozilla' => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4) Gecko/20030624',
63
+ 'Linux Konqueror' => 'Mozilla/5.0 (compatible; Konqueror/3; Linux)',
64
+ 'iPhone' => 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1C28 Safari/419.3',
65
+ 'Mechanize' => "WWW-Mechanize/#{VERSION} (http://rubyforge.org/projects/mechanize/)"
66
+ }
67
+
68
+ attr_accessor :cookie_jar
69
+ attr_accessor :open_timeout, :read_timeout
70
+ attr_accessor :user_agent
71
+ attr_accessor :watch_for_set
72
+ attr_accessor :ca_file
73
+ attr_accessor :key
74
+ attr_accessor :cert
75
+ attr_accessor :pass
76
+ attr_accessor :redirect_ok
77
+ attr_accessor :keep_alive_time
78
+ attr_accessor :keep_alive
79
+ attr_accessor :conditional_requests
80
+ attr_accessor :follow_meta_refresh
81
+ attr_accessor :verify_callback
82
+ attr_accessor :history_added
83
+ attr_accessor :scheme_handlers
84
+ attr_accessor :redirection_limit
85
+
86
+ # A hash of custom request headers
87
+ attr_accessor :request_headers
88
+
89
+ # The HTML parser to be used when parsing documents
90
+ attr_accessor :html_parser
91
+
92
+ attr_reader :history
93
+ attr_reader :pluggable_parser
94
+
95
+ alias :follow_redirect? :redirect_ok
96
+
97
+ @html_parser = Nokogiri::HTML
98
+ class << self; attr_accessor :html_parser, :log end
99
+
100
+ def initialize
101
+ # attr_accessors
102
+ @cookie_jar = CookieJar.new
103
+ @log = nil
104
+ @open_timeout = nil
105
+ @read_timeout = nil
106
+ @user_agent = AGENT_ALIASES['Mechanize']
107
+ @watch_for_set = nil
108
+ @history_added = nil
109
+ @ca_file = nil # OpenSSL server certificate file
110
+
111
+ # callback for OpenSSL errors while verifying the server certificate
112
+ # chain, can be used for debugging or to ignore errors by always
113
+ # returning _true_
114
+ @verify_callback = nil
115
+ @cert = nil # OpenSSL Certificate
116
+ @key = nil # OpenSSL Private Key
117
+ @pass = nil # OpenSSL Password
118
+ @redirect_ok = true # Should we follow redirects?
119
+
120
+ # attr_readers
121
+ @history = WWW::Mechanize::History.new
122
+ @pluggable_parser = PluggableParser.new
123
+
124
+ # Auth variables
125
+ @user = nil # Auth User
126
+ @password = nil # Auth Password
127
+ @digest = nil # DigestAuth Digest
128
+ @auth_hash = {} # Keep track of urls for sending auth
129
+ @request_headers= {} # A hash of request headers to be used
130
+
131
+ # Proxy settings
132
+ @proxy_addr = nil
133
+ @proxy_pass = nil
134
+ @proxy_port = nil
135
+ @proxy_user = nil
136
+
137
+ @conditional_requests = true
138
+
139
+ @follow_meta_refresh = false
140
+ @redirection_limit = 20
141
+
142
+ # Connection Cache & Keep alive
143
+ @connection_cache = {}
144
+ @keep_alive_time = 300
145
+ @keep_alive = true
146
+
147
+ @scheme_handlers = Hash.new { |h,k|
148
+ h[k] = lambda { |link, page|
149
+ raise UnsupportedSchemeError.new(k)
150
+ }
151
+ }
152
+ @scheme_handlers['http'] = lambda { |link, page| link }
153
+ @scheme_handlers['https'] = @scheme_handlers['http']
154
+ @scheme_handlers['relative'] = @scheme_handlers['http']
155
+ @scheme_handlers['file'] = @scheme_handlers['http']
156
+
157
+ @pre_connect_hook = Chain::PreConnectHook.new
158
+ @post_connect_hook = Chain::PostConnectHook.new
159
+
160
+ @html_parser = self.class.html_parser
161
+
162
+ yield self if block_given?
163
+ end
164
+
165
+ def max_history=(length); @history.max_size = length end
166
+ def max_history; @history.max_size end
167
+ def log=(l); self.class.log = l end
168
+ def log; self.class.log end
169
+
170
+ def pre_connect_hooks
171
+ @pre_connect_hook.hooks
172
+ end
173
+
174
+ def post_connect_hooks
175
+ @post_connect_hook.hooks
176
+ end
177
+
178
+ # Sets the proxy address, port, user, and password
179
+ # +addr+ should be a host, with no "http://"
180
+ def set_proxy(addr, port, user = nil, pass = nil)
181
+ @proxy_addr, @proxy_port, @proxy_user, @proxy_pass = addr, port, user, pass
182
+ end
183
+
184
+ # Set the user agent for the Mechanize object.
185
+ # See AGENT_ALIASES
186
+ def user_agent_alias=(al)
187
+ self.user_agent = AGENT_ALIASES[al] || raise("unknown agent alias")
188
+ end
189
+
190
+ # Returns a list of cookies stored in the cookie jar.
191
+ def cookies
192
+ @cookie_jar.to_a
193
+ end
194
+
195
+ # Sets the user and password to be used for authentication.
196
+ def auth(user, password)
197
+ @user = user
198
+ @password = password
199
+ end
200
+ alias :basic_auth :auth
201
+
202
+ # Fetches the URL passed in and returns a page.
203
+ def get(options, parameters = [], referer = nil)
204
+ unless options.is_a? Hash
205
+ url = options
206
+ unless parameters.respond_to?(:each) # FIXME: Remove this in 0.8.0
207
+ referer = parameters
208
+ parameters = []
209
+ end
210
+ else
211
+ raise ArgumentError.new("url must be specified") unless url = options[:url]
212
+ parameters = options[:params] || []
213
+ referer = options[:referer]
214
+ headers = options[:headers]
215
+ end
216
+
217
+ unless referer
218
+ if url.to_s =~ /^http/
219
+ referer = Page.new(nil, {'content-type'=>'text/html'})
220
+ else
221
+ referer = current_page || Page.new(nil, {'content-type'=>'text/html'})
222
+ end
223
+ end
224
+
225
+ # FIXME: Huge hack so that using a URI as a referer works. I need to
226
+ # refactor everything to pass around URIs but still support
227
+ # WWW::Mechanize::Page#base
228
+ unless referer.is_a?(WWW::Mechanize::File)
229
+ referer = referer.is_a?(String) ?
230
+ Page.new(URI.parse(referer), {'content-type' => 'text/html'}) :
231
+ Page.new(referer, {'content-type' => 'text/html'})
232
+ end
233
+
234
+ # fetch the page
235
+ page = fetch_page( :uri => url,
236
+ :referer => referer,
237
+ :headers => headers || {},
238
+ :params => parameters
239
+ )
240
+ add_to_history(page)
241
+ yield page if block_given?
242
+ page
243
+ end
244
+
245
+ ####
246
+ # PUT to +url+ with +query_params+, and setting +options+:
247
+ #
248
+ # put('http://tenderlovemaking.com/', {'q' => 'foo'}, :headers => {})
249
+ #
250
+ def put(url, query_params = {}, options = {})
251
+ page = head(url, query_params, options.merge({:verb => :put}))
252
+ add_to_history(page)
253
+ page
254
+ end
255
+
256
+ ####
257
+ # DELETE to +url+ with +query_params+, and setting +options+:
258
+ #
259
+ # delete('http://tenderlovemaking.com/', {'q' => 'foo'}, :headers => {})
260
+ #
261
+ def delete(url, query_params = {}, options = {})
262
+ page = head(url, query_params, options.merge({:verb => :delete}))
263
+ add_to_history(page)
264
+ page
265
+ end
266
+
267
+ ####
268
+ # HEAD to +url+ with +query_params+, and setting +options+:
269
+ #
270
+ # head('http://tenderlovemaking.com/', {'q' => 'foo'}, :headers => {})
271
+ #
272
+ def head(url, query_params = {}, options = {})
273
+ options = {
274
+ :uri => url,
275
+ :headers => {},
276
+ :params => query_params,
277
+ :verb => :head
278
+ }.merge(options)
279
+ # fetch the page
280
+ page = fetch_page(options)
281
+ yield page if block_given?
282
+ page
283
+ end
284
+
285
+ # Fetch a file and return the contents of the file.
286
+ def get_file(url)
287
+ get(url).body
288
+ end
289
+
290
+ # Clicks the WWW::Mechanize::Link object passed in and returns the
291
+ # page fetched.
292
+ def click(link)
293
+ referer = link.page rescue referer = nil
294
+ href = link.respond_to?(:href) ? link.href :
295
+ (link['href'] || link['src'])
296
+ get(:url => href, :referer => (referer || current_page()))
297
+ end
298
+
299
+ # Equivalent to the browser back button. Returns the most recent page
300
+ # visited.
301
+ def back
302
+ @history.pop
303
+ end
304
+
305
+ # Posts to the given URL wht the query parameters passed in. Query
306
+ # parameters can be passed as a hash, or as an array of arrays.
307
+ # Example:
308
+ # agent.post('http://example.com/', "foo" => "bar")
309
+ # or
310
+ # agent.post('http://example.com/', [ ["foo", "bar"] ])
311
+ def post(url, query={}, headers = {})
312
+ node = {}
313
+ # Create a fake form
314
+ class << node
315
+ def search(*args); []; end
316
+ end
317
+ node['method'] = 'POST'
318
+ node['enctype'] = 'application/x-www-form-urlencoded'
319
+
320
+ form = Form.new(node)
321
+ query.each { |k,v|
322
+ if v.is_a?(IO)
323
+ form.enctype = 'multipart/form-data'
324
+ ul = Form::FileUpload.new(k.to_s,::File.basename(v.path))
325
+ ul.file_data = v.read
326
+ form.file_uploads << ul
327
+ else
328
+ form.fields << Form::Field.new(k.to_s,v)
329
+ end
330
+ }
331
+ post_form(url, form, headers)
332
+ end
333
+
334
+ # Submit a form with an optional button.
335
+ # Without a button:
336
+ # page = agent.get('http://example.com')
337
+ # agent.submit(page.forms.first)
338
+ # With a button
339
+ # agent.submit(page.forms.first, page.forms.first.buttons.first)
340
+ def submit(form, button=nil, headers={})
341
+ form.add_button_to_query(button) if button
342
+ case form.method.upcase
343
+ when 'POST'
344
+ post_form(form.action, form, headers)
345
+ when 'GET'
346
+ get( :url => form.action.gsub(/\?[^\?]*$/, ''),
347
+ :params => form.build_query,
348
+ :headers => headers,
349
+ :referer => form.page
350
+ )
351
+ else
352
+ raise "unsupported method: #{form.method.upcase}"
353
+ end
354
+ end
355
+
356
+ # Returns the current page loaded by Mechanize
357
+ def current_page
358
+ @history.last
359
+ end
360
+
361
+ # Returns whether or not a url has been visited
362
+ def visited?(url)
363
+ ! visited_page(url).nil?
364
+ end
365
+
366
+ # Returns a visited page for the url passed in, otherwise nil
367
+ def visited_page(url)
368
+ if url.respond_to? :href
369
+ url = url.href
370
+ end
371
+ @history.visited_page(resolve(url))
372
+ end
373
+
374
+ # Runs given block, then resets the page history as it was before. self is
375
+ # given as a parameter to the block. Returns the value of the block.
376
+ def transact
377
+ history_backup = @history.dup
378
+ begin
379
+ yield self
380
+ ensure
381
+ @history = history_backup
382
+ end
383
+ end
384
+
385
+ alias :page :current_page
386
+
387
+ private
388
+
389
+ def resolve(url, referer = current_page())
390
+ hash = { :uri => url, :referer => referer }
391
+ chain = Chain.new([
392
+ Chain::URIResolver.new(@scheme_handlers)
393
+ ]).handle(hash)
394
+ hash[:uri].to_s
395
+ end
396
+
397
+ def post_form(url, form, headers = {})
398
+ cur_page = form.page || current_page ||
399
+ Page.new( nil, {'content-type'=>'text/html'})
400
+
401
+ request_data = form.request_data
402
+
403
+ log.debug("query: #{ request_data.inspect }") if log
404
+
405
+ # fetch the page
406
+ page = fetch_page( :uri => url,
407
+ :referer => cur_page,
408
+ :verb => :post,
409
+ :params => [request_data],
410
+ :headers => {
411
+ 'Content-Type' => form.enctype,
412
+ 'Content-Length' => request_data.size.to_s,
413
+ }.merge(headers))
414
+ add_to_history(page)
415
+ page
416
+ end
417
+
418
+ # uri is an absolute URI
419
+ def fetch_page(params)
420
+ options = {
421
+ :request => nil,
422
+ :response => nil,
423
+ :connection => nil,
424
+ :referer => current_page(),
425
+ :uri => nil,
426
+ :verb => :get,
427
+ :agent => self,
428
+ :redirects => 0,
429
+ :params => [],
430
+ :headers => {},
431
+ }.merge(params)
432
+
433
+ before_connect = Chain.new([
434
+ Chain::URIResolver.new(@scheme_handlers),
435
+ Chain::ParameterResolver.new,
436
+ Chain::RequestResolver.new,
437
+ Chain::ConnectionResolver.new(
438
+ @connection_cache,
439
+ @keep_alive,
440
+ @proxy_addr,
441
+ @proxy_port,
442
+ @proxy_user,
443
+ @proxy_pass
444
+ ),
445
+ Chain::SSLResolver.new(@ca_file, @verify_callback, @cert, @key, @pass),
446
+ Chain::AuthHeaders.new(@auth_hash, @user, @password, @digest),
447
+ Chain::HeaderResolver.new(
448
+ @keep_alive,
449
+ @keep_alive_time,
450
+ @cookie_jar,
451
+ @user_agent,
452
+ {}
453
+ ),
454
+ Chain::CustomHeaders.new,
455
+ @pre_connect_hook,
456
+ ])
457
+ before_connect.handle(options)
458
+
459
+ uri = options[:uri]
460
+ request = options[:request]
461
+ cur_page = options[:referer]
462
+ request_data = options[:params]
463
+ redirects = options[:redirects]
464
+ http_obj = options[:connection]
465
+
466
+ # Add If-Modified-Since if page is in history
467
+ if( (page = visited_page(uri)) && page.response['Last-Modified'] )
468
+ request['If-Modified-Since'] = page.response['Last-Modified']
469
+ end if(@conditional_requests)
470
+
471
+ # Specify timeouts if given
472
+ http_obj.open_timeout = @open_timeout if @open_timeout
473
+ http_obj.read_timeout = @read_timeout if @read_timeout
474
+ http_obj.start unless http_obj.started?
475
+
476
+ # Log specified headers for the request
477
+ log.info("#{ request.class }: #{ request.path }") if log
478
+ request.each_header do |k, v|
479
+ log.debug("request-header: #{ k } => #{ v }")
480
+ end if log
481
+
482
+ # Send the request
483
+ attempts = 0
484
+ begin
485
+ response = http_obj.request(request, *request_data) { |r|
486
+ connection_chain = Chain.new([
487
+ Chain::ResponseReader.new(r),
488
+ Chain::BodyDecodingHandler.new,
489
+ ])
490
+ connection_chain.handle(options)
491
+ }
492
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE => x
493
+ log.error("Rescuing EOF error") if log
494
+ http_obj.finish
495
+ raise x if attempts >= 2
496
+ request.body = nil
497
+ http_obj.start
498
+ attempts += 1
499
+ retry
500
+ end
501
+
502
+ after_connect = Chain.new([
503
+ @post_connect_hook,
504
+ Chain::ResponseBodyParser.new(@pluggable_parser, @watch_for_set),
505
+ Chain::ResponseHeaderHandler.new(@cookie_jar, @connection_cache),
506
+ ])
507
+ after_connect.handle(options)
508
+
509
+ res_klass = options[:res_klass]
510
+ response_body = options[:response_body]
511
+ page = options[:page]
512
+
513
+ log.info("status: #{ page.code }") if log
514
+
515
+ if follow_meta_refresh
516
+ redirect_uri = nil
517
+ referer = page
518
+ if (page.respond_to?(:meta) && (redirect = page.meta.first))
519
+ redirect_uri = redirect.uri.to_s
520
+ sleep redirect.node['delay'].to_f
521
+ referer = Page.new(nil, {'content-type'=>'text/html'})
522
+ elsif refresh = response['refresh']
523
+ delay, redirect_uri = Page::Meta.parse(refresh, uri)
524
+ raise StandardError, "Invalid refresh http header" unless delay
525
+ if redirects + 1 > redirection_limit
526
+ raise RedirectLimitReachedError.new(page, redirects)
527
+ end
528
+ sleep delay.to_f
529
+ end
530
+ if redirect_uri
531
+ @history.push(page, page.uri)
532
+ return fetch_page(
533
+ :uri => redirect_uri,
534
+ :referer => referer,
535
+ :params => [],
536
+ :verb => :get,
537
+ :redirects => redirects + 1
538
+ )
539
+ end
540
+ end
541
+
542
+ return page if res_klass <= Net::HTTPSuccess
543
+
544
+ if res_klass == Net::HTTPNotModified
545
+ log.debug("Got cached page") if log
546
+ return visited_page(uri) || page
547
+ elsif res_klass <= Net::HTTPRedirection
548
+ return page unless follow_redirect?
549
+ log.info("follow redirect to: #{ response['Location'] }") if log
550
+ from_uri = page.uri
551
+ raise RedirectLimitReachedError.new(page, redirects) if redirects + 1 > redirection_limit
552
+ redirect_verb = options[:verb] == :head ? :head : :get
553
+ page = fetch_page( :uri => response['Location'].to_s,
554
+ :referer => page,
555
+ :params => [],
556
+ :verb => redirect_verb,
557
+ :redirects => redirects + 1
558
+ )
559
+ @history.push(page, from_uri)
560
+ return page
561
+ elsif res_klass <= Net::HTTPUnauthorized
562
+ raise ResponseCodeError.new(page) unless @user || @password
563
+ raise ResponseCodeError.new(page) if @auth_hash.has_key?(uri.host)
564
+ if response['www-authenticate'] =~ /Digest/i
565
+ @auth_hash[uri.host] = :digest
566
+ if response['server'] =~ /Microsoft-IIS/
567
+ @auth_hash[uri.host] = :iis_digest
568
+ end
569
+ @digest = response['www-authenticate']
570
+ else
571
+ @auth_hash[uri.host] = :basic
572
+ end
573
+ return fetch_page( :uri => uri,
574
+ :referer => cur_page,
575
+ :verb => request.method.downcase.to_sym,
576
+ :params => request_data,
577
+ :headers => options[:headers]
578
+ )
579
+ end
580
+
581
+ raise ResponseCodeError.new(page), "Unhandled response", caller
582
+ end
583
+
584
+ def add_to_history(page)
585
+ @history.push(page, resolve(page.uri))
586
+ history_added.call(page) if history_added
587
+ end
588
+ end
589
+ end