jtzemp-tourbus 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,227 @@
1
+ class WebsickleException < Exception; end
2
+
3
+ module WebSickle
4
+ # form_value is used to interface with the current select form
5
+ attr_reader :form_value
6
+ attr_accessor :page
7
+
8
+ def initialize(options = {})
9
+ @page = nil
10
+ @form_value = HashProxy.new(
11
+ :set => lambda { |identifier, value| set_form_value(identifier, value)},
12
+ :get => lambda { |identifier| get_form_value(identifier)}
13
+ )
14
+ end
15
+
16
+ def click_link(link)
17
+ set_page(agent.click(find_link(link)))
18
+ end
19
+
20
+ def submit_form(options = {})
21
+ options[:button] = :first unless options.has_key?(:button)
22
+ options[:identified_by] ||= :first
23
+ select_form(options[:identified_by])
24
+ set_form_values(options[:values]) if options[:values]
25
+ submit_form_button(options[:button])
26
+ end
27
+
28
+ # select the current form
29
+ def select_form(identifier = {})
30
+ identifier = make_identifier(identifier, [:name, :action, :method])
31
+ @form = find_in_collection(@page.forms, identifier)
32
+ unless @form
33
+ valid_forms = @page.forms.map {|f| "name: #{f.name}, method: #{f.method}, action: #{f.action}"} * "\n"
34
+ report_error("Couldn't find form on page at #{@page.uri} with attributes #{identifier.inspect}. Valid forms on this page are: \n#{valid_forms}")
35
+ end
36
+ @form
37
+ end
38
+
39
+ # submits the current form
40
+ def submit_form_button(button_criteria = nil, options = {})
41
+ button =
42
+ case button_criteria
43
+ when nil
44
+ nil
45
+ else
46
+ find_button(button_criteria)
47
+ end
48
+ set_page(agent.submit(@form, button))
49
+ end
50
+
51
+ # sets the given path to the current page, then opens it using our agent
52
+ def open_page(path, parameters = [], referer = nil)
53
+ set_page(agent.get(path, parameters, referer))
54
+ end
55
+
56
+ # uses Hpricot style css selectors to find the elements in the current +page+.
57
+ # Uses Hpricot#/ (or Hpricot#search)
58
+ def select_element(match)
59
+ select_element_in(@page, match)
60
+ end
61
+
62
+ # uses Hpricot style css selectors to find the element in the given container. Works with html pages, and file pages that happen to have xml-like content.
63
+ # throws error if can't find a match
64
+ def select_element_in(contents, match)
65
+ result = (contents.respond_to?(:/) ? contents : Hpricot(contents.body)) / match
66
+ if result.blank?
67
+ report_error("Tried to find element matching #{match}, but couldn't")
68
+ else
69
+ result
70
+ end
71
+ end
72
+
73
+ # uses Hpricot style css selectors to find the element. Works with html pages, and file pages that happen to have xml-like content.
74
+ # throws error if can't find a match
75
+ # Uses Hpricot#at
76
+ def detect_element(match)
77
+ result = (@page.respond_to?(:at) ? @page : Hpricot(@page.body)).at(match)
78
+ if result.blank?
79
+ report_error("Tried to find element matching #{match}, but couldn't")
80
+ else
81
+ result
82
+ end
83
+ end
84
+
85
+ protected
86
+ # our friendly mechinze agent
87
+ def agent
88
+ @agent ||= new_mechanize_agent
89
+ end
90
+
91
+ def make_identifier(identifier, valid_keys = nil, default_key = :name)
92
+ identifier = {default_key => identifier} unless identifier.is_a?(Hash) || identifier.is_a?(Symbol)
93
+ identifier.assert_valid_keys(valid_keys) if identifier.is_a?(Hash) && valid_keys
94
+ identifier
95
+ end
96
+
97
+ def find_field(identifier)
98
+ if @form.nil?
99
+ report_error("No form is selected when trying to find field by #{identifier.inspect}")
100
+ return
101
+ end
102
+ identifier = make_identifier(identifier, [:name, :value])
103
+ find_in_collection(@form.radiobuttons + @form.fields + @form.checkboxes + @form.file_uploads, identifier) ||
104
+ report_error("Tried to find field identified by #{identifier.inspect}, but failed.\nForm fields are: #{(@form.radiobuttons + @form.fields + @form.checkboxes + @form.file_uploads).map{|f| f.name} * ", \n "}")
105
+ end
106
+
107
+ def find_link(identifier)
108
+ identifier = make_identifier(identifier, [:href, :text], :text)
109
+ find_in_collection(page.links, identifier) ||
110
+ report_error("Tried to find link identified by #{identifier.inspect}, but failed.\nValid links are: #{page.links.map{|f| f.inspect} * ", \n "}")
111
+ end
112
+
113
+ # finds a button by parameters. Throws error if not able to find.
114
+ # example:
115
+ # find_button("btnSubmit") - finds a button named "btnSubmit"
116
+ # find_button(:name => "btnSubmit")
117
+ # find_button(:name => "btnSubmit", :value => /Lucky/) - finds a button named btnSubmit with a value matching /Lucky/
118
+ def find_button(identifier)
119
+ identifier = make_identifier(identifier, [:value, :name])
120
+ find_in_collection(@form.buttons, identifier) ||
121
+ report_error("Tried to find button identified by #{identifier.inspect}, but failed. Buttons on selected form are: #{@form.buttons.map{|f| f.name} * ','}")
122
+ end
123
+
124
+ # the magic method that powers find_button, find_field. Does not throw an error if not found
125
+ def find_in_collection(collection, identifier, via = :find)
126
+ return collection.first if identifier == :first
127
+ find_all_in_collection(collection, identifier, :find)
128
+ end
129
+
130
+ def find_all_in_collection(collection, identifier, via = :select)
131
+ return [collection.first] if identifier == :first
132
+ collection.send(via) do |item|
133
+ identifier.all? { |k, criteria| is_a_match?(criteria, item.send(k)) }
134
+ end
135
+ end
136
+
137
+ # sets a form-field's value by identifier. Throw's error if field does not exist
138
+ def set_form_value(identifier, value)
139
+ field = find_field(identifier)
140
+ case field
141
+ when WWW::Mechanize::Form::CheckBox
142
+ field.checked = value
143
+ when WWW::Mechanize::Form::RadioButton
144
+ radio_collection = find_all_in_collection(@form.radiobuttons, :name => field.name)
145
+ radio_collection.each { |f|; f.checked = false }
146
+ finder = (value.is_a?(Hash) || value.is_a?(Symbol)) ? value : {:value => value}
147
+ find_in_collection(radio_collection, finder).checked = true
148
+ when WWW::Mechanize::Form::SelectList
149
+ if value.is_a?(Hash) || value.is_a?(Symbol)
150
+ field.value = find_in_collection(field.options, value).value
151
+ else
152
+ field.value = value
153
+ end
154
+ else
155
+ field.value = value
156
+ end
157
+ end
158
+
159
+ def set_form_values(set_pairs = {})
160
+ flattened_value_hash(set_pairs).each do |identifier, value|
161
+ set_form_value(identifier, value)
162
+ end
163
+ end
164
+
165
+ def flattened_value_hash(hash, parents = [])
166
+ new_hash = {}
167
+ hash.each do |key, value|
168
+ if value.is_a?(Hash) && value.keys.first.is_a?(String)
169
+ new_hash.update(flattened_value_hash(value, [key] + parents))
170
+ else
171
+ parents.each { |parent| key = "#{parent}[#{key}]"}
172
+ new_hash[key] = value
173
+ end
174
+ end
175
+ new_hash
176
+ end
177
+
178
+ # sets a form-field's value by identifier. Throw's error if field does not exist
179
+ def get_form_value(identifier)
180
+ field = find_field(identifier)
181
+ case field
182
+ when WWW::Mechanize::Form::CheckBox
183
+ field.checked
184
+ else
185
+ field.value
186
+ end
187
+ end
188
+
189
+ def format_error(msg)
190
+ error = "Error encountered: #{msg}."
191
+ begin
192
+ error << "\n\nPage URL:#{@page.uri.to_s}" if @page
193
+ rescue
194
+ end
195
+ error
196
+ end
197
+
198
+ def report_error(msg)
199
+ raise WebsickleException, format_error(msg)
200
+ nil
201
+ end
202
+
203
+ private
204
+ def set_page(p)
205
+ @form = nil
206
+ @page = p
207
+ end
208
+
209
+ def is_a_match?(criteria, value)
210
+ case criteria
211
+ when Regexp
212
+ criteria.match(value)
213
+ when String
214
+ criteria == value
215
+ when Array
216
+ criteria.include?(value)
217
+ else
218
+ criteria.to_s == value.to_s
219
+ end
220
+ end
221
+
222
+ def new_mechanize_agent
223
+ a = WWW::Mechanize.new
224
+ a.read_timeout = 600 # 10 minutes
225
+ a
226
+ end
227
+ end
@@ -0,0 +1,137 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe WebSickle::Helpers::TableReader do
4
+ describe "Simple example" do
5
+ before(:each) do
6
+ @content = <<-EOF
7
+ <table>
8
+ <tr>
9
+ <th>Name</th>
10
+ <th>Age</th>
11
+ </tr>
12
+ <tr>
13
+ <td>Googly</td>
14
+ <td>2</td>
15
+ </tr>
16
+ </table>
17
+ EOF
18
+ h = Hpricot(@content)
19
+ @table = WebSickle::Helpers::TableReader.new(h / "table")
20
+ end
21
+
22
+ it "should extract headers" do
23
+ @table.headers.should == ["Name", "Age"]
24
+ end
25
+
26
+ it "should extract rows" do
27
+ @table.rows.should == [
28
+ {"Name" => "Googly", "Age" => "2"}
29
+ ]
30
+ end
31
+ end
32
+
33
+
34
+
35
+ describe "Targetted example" do
36
+ before(:each) do
37
+ @content = <<-EOF
38
+ <table>
39
+ <thead>
40
+ <tr>
41
+ <td colspan='2'>----</td>
42
+ </tr>
43
+ <tr>
44
+ <th><b>Name</b></th>
45
+ <th><b>Age</b></th>
46
+ </tr>
47
+ </thead>
48
+ <tbody>
49
+ <tr>
50
+ <td>Googly</td>
51
+ <td>2</td>
52
+ </tr>
53
+ <tr>
54
+ <td>Bear</td>
55
+ <td>3</td>
56
+ </tr>
57
+ <tr>
58
+ <td colspan='2'>Totals!</td>
59
+ </tr>
60
+ <tr>
61
+ <td>---</td>
62
+ <td>5</td>
63
+ </tr>
64
+ </tbody>
65
+ </table>
66
+ EOF
67
+ h = Hpricot(@content)
68
+ @table = WebSickle::Helpers::TableReader.new(h / " > table",
69
+ :header_selector => " > th > b",
70
+ :header_offset => 1,
71
+ :body_range => 2..-3
72
+ )
73
+ end
74
+
75
+ it "should extract the column headers" do
76
+ @table.headers.should == ["Name", "Age"]
77
+ end
78
+
79
+ it "should extract the row data for the specified range" do
80
+ @table.rows.should == [
81
+ {"Name" => "Googly", "Age" => "2"},
82
+ {"Name" => "Bear", "Age" => "3"},
83
+ ]
84
+ end
85
+
86
+ it "should allow you to check extra rows to assert you didn't chop off too much" do
87
+ (@table.extra_rows.first / "td").inner_text.should == "Totals!"
88
+ end
89
+ end
90
+
91
+
92
+
93
+ describe "when using procs to extract data" do
94
+ before(:each) do
95
+ @content = <<-EOF
96
+ <table>
97
+ <tr>
98
+ <th>Name</th>
99
+ <th>Age</th>
100
+ </tr>
101
+ <tr>
102
+ <td>Googly</td>
103
+ <td>2</td>
104
+ </tr>
105
+ <tr>
106
+ <td>Bear</td>
107
+ <td>3</td>
108
+ </tr>
109
+ </table>
110
+ EOF
111
+ h = Hpricot(@content)
112
+ @table = WebSickle::Helpers::TableReader.new(h / " > table",
113
+ :header_proc => lambda {|th| th.inner_text.downcase.to_sym},
114
+ :body_proc => lambda {|col_name, td|
115
+ value = td.inner_text
116
+ case col_name
117
+ when :name
118
+ value.upcase
119
+ when :age
120
+ value.to_i
121
+ end
122
+ }
123
+ )
124
+ end
125
+
126
+ it "should use the header proc to extract column headers" do
127
+ @table.headers.should == [:name, :age]
128
+ end
129
+
130
+ it "should use the body proc to format the data" do
131
+ @table.rows.should == [
132
+ {:name => "GOOGLY", :age => 2},
133
+ {:name => "BEAR", :age => 3}
134
+ ]
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/../init.rb'
2
+ require 'rubygems'
3
+ require 'hpricot'
4
+ require 'test/unit'
5
+ require 'spec'
6
+ require 'active_support'
7
+ require File.dirname(__FILE__) + '/spec_helpers/mechanize_mock_helper.rb'
@@ -0,0 +1,12 @@
1
+ module MechanizeMockHelper
2
+ def fixture_file(filename)
3
+ File.read("#{File.dirname(__FILE__)}/../fixtures/#{filename}")
4
+ end
5
+
6
+ def mechanize_page(path_to_data, options = {})
7
+ options[:uri] ||= URI.parse("http://url.com/#{path_to_data}")
8
+ options[:response] ||= {'content-type' => 'text/html'}
9
+
10
+ WWW::Mechanize::Page.new(options[:uri], options[:response], fixture_file("/#{path_to_data}"))
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class WebSickleHelper
4
+ include WebSickle
5
+ end
6
+
7
+ describe WebSickle do
8
+ include MechanizeMockHelper
9
+
10
+ before(:all) do
11
+ WebSickleHelper.protected_instance_methods.each do |method|
12
+ WebSickleHelper.send(:public, method)
13
+ end
14
+ end
15
+
16
+ before(:each) do
17
+ @helper = WebSickleHelper.new
18
+ end
19
+
20
+ it "should flatten a value hash" do
21
+ @helper.flattened_value_hash("contact" => {"first_name" => "bob"}).should == {"contact[first_name]" => "bob"}
22
+ end
23
+
24
+ describe "clicking links" do
25
+ before(:each) do
26
+ @helper.stub!(:page).and_return(mechanize_page("linkies.html"))
27
+ end
28
+
29
+ it "should click a link by matching the link text" do
30
+ @helper.agent.should_receive(:click) do |link|
31
+ link.text.should include("one")
32
+ end
33
+ @helper.click_link(:text => /one/)
34
+ end
35
+
36
+ it "should click a link by matching the link href" do
37
+ @helper.agent.should_receive(:click) do |link|
38
+ link.href.should include("/two")
39
+ end
40
+ @helper.click_link(:href => %r{/two})
41
+ end
42
+
43
+ it "should default matching the link text" do
44
+ @helper.agent.should_receive(:click) do |link|
45
+ link.text.should include("Link number one")
46
+ end
47
+ @helper.click_link("Link number one")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ require 'webrat'
2
+ require 'webrat/mechanize'
3
+
4
+ module WebSickleWebratAdapter
5
+
6
+ def open_page(url)
7
+ session.visit(url)
8
+ end
9
+
10
+ def click_link(options)
11
+ raise "This adapter only works with the :text option!" unless options[:text]
12
+ session.click_link(options[:text])
13
+ end
14
+
15
+ def submit_form_with_options(options_hash)
16
+ action = options_hash[:identified_by][:action]
17
+ raise "You must provide an action!" unless action
18
+ action_regexp = action.is_a?(Regexp) ? action : Regexp.new("#{action}$")
19
+ form = page.forms.detect {|f| f.action =~ action_regexp }
20
+ raise "Could not find form with matching action: #{action_regexp}" unless form
21
+ method = options_hash[:method] || 'post'
22
+ session.request_page(form.action, method, options_hash[:values])
23
+ end
24
+
25
+ def page
26
+ session.response
27
+ end
28
+
29
+ # def visit(url)
30
+ # session.visit(url)
31
+ # end
32
+
33
+ private
34
+
35
+ def session
36
+ @session ||= Webrat::MechanizeSession.new
37
+ end
38
+
39
+
40
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jtzemp-tourbus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.9
5
+ platform: ruby
6
+ authors:
7
+ - David Brady
8
+ - James Britt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-04-17 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: mechanize
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.8.5
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: trollop
28
+ type: :runtime
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ version:
36
+ - !ruby/object:Gem::Dependency
37
+ name: faker
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ - !ruby/object:Gem::Dependency
47
+ name: hpricot
48
+ type: :runtime
49
+ version_requirement:
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ description: TourBus, a web stress-testing tool that combines complex 'tour' definitions with scalable concurrent testing
57
+ email: github@shinybit.com
58
+ executables:
59
+ - tourbus
60
+ - tourwatch
61
+ extensions: []
62
+
63
+ extra_rdoc_files:
64
+ - README.rdoc
65
+ - MIT-LICENSE
66
+ - examples/contact_app/README.rdoc
67
+ files:
68
+ - bin/tourbus
69
+ - bin/tourwatch
70
+ - examples/contact_app/README.rdoc
71
+ - examples/contact_app/contact_app.rb
72
+ - examples/contact_app/tours/simple.rb
73
+ - examples/contact_app/tours/tourbus.yml
74
+ - lib/common.rb
75
+ - lib/runner.rb
76
+ - lib/tour.rb
77
+ - lib/tour_bus.rb
78
+ - lib/tour_watch.rb
79
+ - lib/web_sickle_webrat_adapter.rb
80
+ - lib/web-sickle/init.rb
81
+ - lib/web-sickle/lib/assertions.rb
82
+ - lib/web-sickle/lib/hash_proxy.rb
83
+ - lib/web-sickle/lib/helpers/asp_net.rb
84
+ - lib/web-sickle/lib/helpers/table_reader.rb
85
+ - lib/web-sickle/lib/make_nokigiri_output_useful.rb
86
+ - lib/web-sickle/lib/web_sickle.rb
87
+ - lib/web-sickle/spec/lib/helpers/table_reader_spec.rb
88
+ - lib/web-sickle/spec/spec_helper.rb
89
+ - lib/web-sickle/spec/spec_helpers/mechanize_mock_helper.rb
90
+ - lib/web-sickle/spec/web_sickle_spec.rb
91
+ - README.rdoc
92
+ - MIT-LICENSE
93
+ has_rdoc: true
94
+ homepage: http://github.com/dbrady/tourbus/
95
+ post_install_message:
96
+ rdoc_options:
97
+ - --line-numbers
98
+ - --inline-source
99
+ - --main
100
+ - README.rdoc
101
+ - --title
102
+ - Tourbus - Web Stress Testing in Ruby
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: "0"
110
+ version:
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: "0"
116
+ version:
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.2.0
121
+ signing_key:
122
+ specification_version: 2
123
+ summary: TourBus web stress-testing tool
124
+ test_files: []
125
+