qa_robusta 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/.gemtest +0 -0
- data/History.txt +6 -0
- data/Manifest.txt +101 -0
- data/README.txt +48 -0
- data/Rakefile +18 -0
- data/bin/qa_robusta +14 -0
- data/common/Rakefile +95 -0
- data/common/conf/monkey_patch.yaml +8 -0
- data/common/conf/monkey_patch.yaml.ex +10 -0
- data/common/lib/array.rb +9 -0
- data/common/lib/cache.rb +12 -0
- data/common/lib/constants.rb +35 -0
- data/common/lib/error_defns.rb +13 -0
- data/common/lib/format_html_tmp.html +34 -0
- data/common/lib/formatters.rb +18 -0
- data/common/lib/gem_helpers.rb +29 -0
- data/common/lib/gems/.svn/entries +43 -0
- data/common/lib/gems/cache/.svn/entries +28 -0
- data/common/lib/gems/doc/.svn/entries +28 -0
- data/common/lib/gems/gems/.svn/entries +28 -0
- data/common/lib/gems/installed/.svn/entries +40 -0
- data/common/lib/gems/installed/cache/.svn/entries +28 -0
- data/common/lib/gems/installed/doc/.svn/entries +28 -0
- data/common/lib/gems/installed/gems/.svn/entries +28 -0
- data/common/lib/gems/installed/specifications/.svn/entries +28 -0
- data/common/lib/gems/specifications/.svn/entries +28 -0
- data/common/lib/gen_suite_doc.rb +52 -0
- data/common/lib/load_test_data.rb +18 -0
- data/common/lib/monkey_patch.rb +149 -0
- data/common/lib/navigate_mech.rb +79 -0
- data/common/lib/update_element.rb +12 -0
- data/common/monkey_patches/mechanize.rb +589 -0
- data/common/monkey_patches/table.rb +363 -0
- data/common/monkey_patches/telnet.rb +420 -0
- data/common/monkey_patches/unit.rb +538 -0
- data/demo/demo_site.rb +38 -0
- data/demo/public/javascripts/jquery-1.6.2.min.js +18 -0
- data/demo/views/index.erb +35 -0
- data/lib/monkey_patch.rb +26 -0
- data/lib/qa_robusta.rb +3 -0
- data/mechanize_interface/conf/app.yaml +10 -0
- data/mechanize_interface/lib/agent.rb +18 -0
- data/mechanize_interface/lib/app_require.rb +19 -0
- data/mechanize_interface/lib/get_page.rb +20 -0
- data/mechanize_interface/lib/login.rb +30 -0
- data/mechanize_interface/lib/navigation_paths.rb +12 -0
- data/mechanize_interface/test/lib/mech_unit_test.rb +19 -0
- data/qa_observer/conf/dev_users.yaml +9 -0
- data/qa_observer/conf/development.yaml +3 -0
- data/qa_observer/conf/qa_observer_links.yaml +10 -0
- data/qa_observer/generators/site/site_generator.rb +61 -0
- data/qa_observer/generators/site/templates/base_urls.yaml.erb +6 -0
- data/qa_observer/generators/site/templates/create_flow.rb.erb +54 -0
- data/qa_observer/generators/site/templates/elements.rb.erb +61 -0
- data/qa_observer/generators/site/templates/html_elements.yaml +22 -0
- data/qa_observer/generators/site/templates/navigate.rb +32 -0
- data/qa_observer/generators/site/templates/reports.rb +7 -0
- data/qa_observer/generators/site/templates/users_list.yaml +21 -0
- data/qa_observer/generators/test_case/templates/test_case.rb.erb +62 -0
- data/qa_observer/generators/test_case/test_case_generator.rb +29 -0
- data/qa_observer/lib/doc.rb +103 -0
- data/qa_observer/lib/env_details.rb +2 -0
- data/qa_observer/lib/error_defns.rb +3 -0
- data/qa_observer/lib/form_helpers.rb +52 -0
- data/qa_observer/lib/reports.rb +7 -0
- data/qa_observer/lib/requires.rb +23 -0
- data/qa_observer/lib/system_test.rb +146 -0
- data/qa_observer/lib/test_extention.rb +29 -0
- data/qa_observer/lib/update_element.rb +12 -0
- data/qa_observer/lib/watir.rb +42 -0
- data/qa_observer/script/destroy +14 -0
- data/qa_observer/script/generate +14 -0
- data/qa_observer/sites/demo/conf/base_urls.yaml +6 -0
- data/qa_observer/sites/demo/conf/html_elements.yaml +22 -0
- data/qa_observer/sites/demo/conf/remote_machine.yaml +12 -0
- data/qa_observer/sites/demo/conf/users_list.yaml +21 -0
- data/qa_observer/sites/demo/elements/demo_elements.rb +9 -0
- data/qa_observer/sites/demo/flows/demo_flows.rb +37 -0
- data/qa_observer/sites/demo/helpers/.placeholder +0 -0
- data/qa_observer/sites/demo/lib/navigate.rb +32 -0
- data/qa_observer/sites/demo/lib/reports.rb +7 -0
- data/qa_observer/sites/demo/results/.placeholder +0 -0
- data/qa_observer/sites/demo/results/images/.placeholder +0 -0
- data/qa_observer/sites/demo/test_cases/home_page.rb +106 -0
- data/qa_observer/suites/demo_lg_chrome/lg.yaml +11 -0
- data/qa_observer/suites/demo_md_chrome/md.yaml +11 -0
- data/qa_observer/suites/demo_sm_chrome/sm.yaml +11 -0
- data/qa_observer/suites/init.rb +41 -0
- data/qa_observer/test_runner.rb +125 -0
- data/remote_unix/helpers/constants.rb +8 -0
- data/remote_unix/helpers/out_xforms.rb +48 -0
- data/remote_unix/lib/common.rb +52 -0
- data/remote_unix/lib/error_defn.rb +4 -0
- data/remote_unix/lib/general_unix.rb +308 -0
- data/remote_unix/lib/network_commands.rb +41 -0
- data/remote_unix/lib/network_info.rb +98 -0
- data/remote_unix/lib/postfix_commands.rb +87 -0
- data/remote_unix/lib/postfix_info.rb +30 -0
- data/remote_unix/lib/requires.rb +25 -0
- data/remote_unix/lib/scp.rb +22 -0
- data/remote_unix/lib/ssh.rb +46 -0
- data/test/test_qa_robusta.rb +8 -0
- 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
|