qa_robusta 0.1.3

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.
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