dbrady-tourbus 0.0.9 → 0.1.1

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.
@@ -7,7 +7,7 @@ Flexible and scalable website testing tool.
7
7
  * David Brady -- david.brady@leadmediapartners.com
8
8
  * Tim Harper -- tim.harper@leadmediapartners.com
9
9
  * James Britt -- james@neurogami.com
10
- * JTZemp -- jt.zemp@leadmediapartners.com
10
+ * JT Zemp -- jtzemp@gmail.com
11
11
 
12
12
 
13
13
  == General Info
@@ -20,6 +20,10 @@ testing tool. The difference is that TourBus also scales concurrently,
20
20
  and you can perform hundreds of complicated regression tests
21
21
  simultaneously in order to thoroughly load test your website.
22
22
 
23
+ It uses Webrat::Mechanize to run the browsing session, so you get the
24
+ load testing you want, and all the sweetness of Webrat to write your
25
+ tests in.
26
+
23
27
  == Motivation
24
28
 
25
29
  I started writing TourBus because I needed flexibility and scalability
@@ -41,12 +45,12 @@ better name, these are called Tours.
41
45
  * Make a folder called tours and put a file in it called simple.rb. In
42
46
  it write:
43
47
 
44
- class Simple < Tour
45
- def test_homepage
46
- open_page "http://#{@host}/"
47
- assert_page_body_contains "My Home Page"
48
- end
49
- end
48
+ class Simple < Tour
49
+ def test_homepage
50
+ visit "http://#{@host}/"
51
+ assert_contain "My Home Page"
52
+ end
53
+ end
50
54
 
51
55
  * Files in ./tours should have classes that match their names. E.g.
52
56
  "class BigHairyTest < Tour" belongs in ./tours/big_hairy_test.rb
@@ -79,7 +83,7 @@ of the methods in Simple three times.
79
83
 
80
84
  * You can specify multiple tours.
81
85
 
82
- tourbus -c 2 -n 3 simple1 simple2 simple3
86
+ tourbus -c 2 -n 3 simple1 simple2 simple3
83
87
 
84
88
  * If you don't specify a tour, all tours in ./tours will be run.
85
89
 
@@ -87,7 +91,7 @@ of the methods in Simple three times.
87
91
 
88
92
  * You can run tours and filter given tests.
89
93
 
90
- tourbus -c 2 -n 3 simple -t test_login,test_logout
94
+ tourbus -c 2 -n 3 simple -t test_login,test_logout
91
95
 
92
96
  Note that if you specify multiple tours and filter tests, the filtered
93
97
  tests will be run on all tours specified. If you do not specify a
@@ -178,7 +182,9 @@ duplications, oversights, and kludges.
178
182
  * I'd like to remove WebSickle and replace it with Webrat. There is a
179
183
  webrat branch on the main fork (http://github.com/dbrady/tourbus)
180
184
  that is 90% complete. Once that's done we can start massaging the
181
- API to be a little more friendly.
185
+ API to be a little more friendly. [done (but now that it is, it
186
+ needs a refactoring--Tour should probably inherit from
187
+ Webrat::Mechanize, not delegate to it.)]
182
188
 
183
189
  == Credits
184
190
 
@@ -75,60 +75,54 @@ particular order.
75
75
 
76
76
  Right. Let's look test_home first, because it's simpler:
77
77
 
78
- def test_home
79
- open_site_page "/"
80
- click_link :text => /Enter Contact/
81
- assert_page_uri_matches "/contacts"
82
- end
78
+ def test_home
79
+ visit "/"
80
+ assert_contain "If you click this"
83
81
 
84
- +open_site_page+ is defined in Tour.rb, it opens the given path on the
82
+ click_link "Enter Contact"
83
+ assert_match /\/contacts/, current_page.url
84
+ end
85
+
86
+ +visit+ is a webrat method that you can call inside of your tours. It opens the given path on the
85
87
  host that tourbus is testing.
86
88
 
89
+ +assert_contain+ is also a webrat method that confirms the given string is on the page.
90
+
87
91
  +click_link+ does what you'd expect. It takes a hash that identifies
88
- the link to click. In this case I chose to identify the link with a
89
- regexp describing its text label. +click_link+ will raise an exception
92
+ the link to click. +click_link+ will raise an exception
90
93
  if it cannot find the link to click.
91
94
 
92
- +assert_page_uri_matches+ will raise an exception unless the uri
93
- matches the given string or regexp. If I had passed in a regexp, it
94
- would have passed if the regexp matched. *Note:* Strings only match at
95
- the /end/ of the uri; simple containment is not enough. Passing
96
- "/contacts" works the same as passing %r{/contacts$}.
97
-
98
- Clear as mud? "/contacts" would match
99
- http://localhost:4567/users/42/contacts but not
100
- http://localhost:4567/contacts/42.
95
+ +assert_match+ comes from Test::Unit which is used internally to webrat. It will raise an exception unless the uri
96
+ matches the given regexp.
101
97
 
98
+ So you should be able to use any Webrat locator or matcher, and any of the Test::Unit assertions.
102
99
 
103
100
  === test_contacts
104
101
 
105
102
  Okay, let's actually submit a form.
106
103
 
107
- def test_contacts
108
- open_site_page "contacts"
109
- submit_form(
110
- :identified_by => { :action => %r{/contacts} },
111
- :values => {
112
- 'first_name' => "Joe",
113
- 'last_name' => "Tester"
114
- }
115
- )
116
- assert_page_uri_matches "/contacts"
117
- assert_page_body_contains "Tester, Joe"
118
- end
104
+ def test_contacts
105
+ visit "/contacts"
106
+
107
+ fill_in "first_name", :with => "Joe"
108
+ fill_in "last_name", :with => "Tester"
109
+ click_button
110
+
111
+ assert_contain "Tester, Joe"
112
+ end
119
113
 
120
114
  test_contacts starts by going directly to the contacts app. Note that
121
- the leading "/" is optional.
115
+ the leading "/" isn't optional.
116
+
117
+ +fill_in+ is a Webrat method that will look for form fields based on ids, label text, and other things.
118
+ It's matchers are pretty good. Check out Webrat's documentation for more info. In the examples above,
119
+ we're finding the fields for the first name and last name and putting in "Joe" and "Tester" respectively.
120
+ +fill_in+ asserts that the fields actually exist and will raise an exception if they don't.
122
121
 
123
- +submit_form+ does what its name implies. It finds the correct form to
124
- submit by matching the action to a regexp, then it sets the form
125
- values and submits the form. *Note:* Like +click_link+, +submit_form+
126
- contains some implicit assertions. It actually reads the form looking
127
- for the named inputs and will raise an exception if any are missing.
128
- This means you cannot use submit_form to do a blind post to a
129
- webserver.
122
+ +click_button+ does what its name implies. It finds the correct form to
123
+ submit Webrat is smart like that. *Note:* Like +click_link+, +click_button+
124
+ contains some implicit assertions and will raise an exception if the button doesn't exist.
130
125
 
131
- +assert_page_uri_matches+ we've already seen;
132
- +assert_page_body_contains+ searches the page body for the given text
133
- or regexp.
126
+ +assert_contain+ we've already seen.
134
127
 
128
+ Good luck, and happy touring!
@@ -9,7 +9,7 @@ require 'rubygems'
9
9
  require 'sinatra'
10
10
 
11
11
  get '/' do
12
- '<a href="/contacts">Enter Contact</a>'
12
+ %{If you click this, I'll take you to a page where you can enter your contact info: <a href="/contacts">Enter Contact</a>}
13
13
  end
14
14
 
15
15
  get '/contacts' do
@@ -23,7 +23,7 @@ get '/contacts' do
23
23
  <form action="/contacts" method="POST">
24
24
  <p><label for="first_name"><b>First Name:</b></label> <input name="first_name" size="30"></p>
25
25
  <p><label for="last_name"><b>Last Name:</b></label> <input name="last_name" size="30"></p>
26
- <input type="submit">
26
+ <input type="submit" value="Submit">
27
27
  </form>
28
28
  </body>
29
29
  </html>
@@ -1,20 +1,19 @@
1
1
  class Simple < Tour
2
2
  def test_home
3
- open_site_page "/"
4
- click_link :text => /Enter Contact/
5
- assert_page_uri_matches "/contacts"
3
+ visit "/"
4
+ assert_contain "If you click this"
5
+
6
+ click_link "Enter Contact"
7
+ assert_match /\/contacts/, current_page.url
6
8
  end
7
9
 
8
10
  def test_contacts
9
- open_site_page "contacts"
10
- submit_form(
11
- :identified_by => { :action => %r{/contacts} },
12
- :values => {
13
- 'first_name' => "Joe",
14
- 'last_name' => "Tester"
15
- }
16
- )
17
- assert_page_uri_matches "/contacts"
18
- assert_page_body_contains "Tester, Joe"
11
+ visit "/contacts"
12
+
13
+ fill_in "first_name", :with => "Joe"
14
+ fill_in "last_name", :with => "Tester"
15
+ click_button
16
+
17
+ assert_contain "Tester, Joe"
19
18
  end
20
19
  end
@@ -1,2 +1,2 @@
1
1
  ---
2
- host: localhost
2
+ host: http://localhost:4567
@@ -16,7 +16,6 @@ require 'activesupport'
16
16
 
17
17
  require 'monitor'
18
18
  require 'faker'
19
- require 'web-sickle/init'
20
19
  require 'tour_bus'
21
20
  require 'runner'
22
21
  require 'tour'
@@ -1,6 +1,9 @@
1
1
  require 'monitor'
2
2
  require 'common'
3
3
 
4
+ # The common base class for all exceptions raised by Webrat.
5
+ class WebratError < StandardError ; end
6
+
4
7
  class Runner
5
8
  attr_reader :host, :tours, :number, :runner_type, :runner_id
6
9
 
@@ -31,7 +34,7 @@ class Runner
31
34
  tests += 1
32
35
  tour.run_test test
33
36
  passes += 1
34
- rescue TourBusException, WebsickleException => e
37
+ rescue TourBusException, WebratError => e
35
38
  log("********** FAILURE IN RUN! **********")
36
39
  log e.message
37
40
  e.backtrace.each do |trace|
@@ -1,5 +1,9 @@
1
+ require 'forwardable'
1
2
  require 'monitor'
2
3
  require 'common'
4
+ require 'webrat'
5
+ require 'webrat/mechanize'
6
+ require 'test/unit/assertions'
3
7
 
4
8
  # A tour is essentially a test suite file. A Tour subclass
5
9
  # encapsulates a set of tests that can be done, and may contain helper
@@ -8,12 +12,68 @@ require 'common'
8
12
  # that area and create test_ methods for each type of test to be done.
9
13
 
10
14
  class Tour
11
- include WebSickle
12
- attr_reader :host, :tours, :number, :tour_type, :tour_id
13
-
15
+ extend Forwardable
16
+ include Webrat::Matchers
17
+ include Webrat::SaveAndOpenPage
18
+ include Test::Unit::Assertions
19
+
20
+ attr_reader :host, :tours, :number, :tour_type, :tour_id, :webrat_session
21
+
22
+ # delegate goodness to webrat
23
+ [
24
+ :fill_in,
25
+ :fills_in,
26
+ :set_hidden_field,
27
+ :submit_form,
28
+ :check,
29
+ :checks,
30
+ :uncheck,
31
+ :unchecks,
32
+ :choose,
33
+ :chooses,
34
+ :current_page,
35
+ :select,
36
+ :selects,
37
+ :select_datetime,
38
+ :selects_datetime,
39
+ :select_date,
40
+ :selects_date,
41
+ :select_time,
42
+ :selects_time,
43
+ :attach_file,
44
+ :attaches_file,
45
+ :click_area,
46
+ :clicks_area,
47
+ :click_link,
48
+ :clicks_link,
49
+ :click_button,
50
+ :clicks_button,
51
+ :field_labeled,
52
+ :field_by_xpath,
53
+ :field_with_id,
54
+ :select_option,
55
+ :automate,
56
+ :basic_auth,
57
+ :check_for_infinite_redirects,
58
+ :click_link_within,
59
+ :dom,
60
+ :header,
61
+ :http_accept,
62
+ :infinite_redirect_limit_exceeded?,
63
+ :internal_redirect?,
64
+ :redirected_to,
65
+ :reload,
66
+ :response_body,
67
+ :simulate,
68
+ :visit,
69
+ :within,
70
+ :xml_content_type?].each {|m| def_delegators(:webrat_session, m) }
71
+
14
72
  def initialize(host, tours, number, tour_id)
15
73
  @host, @tours, @number, @tour_id = host, tours, number, tour_id
16
74
  @tour_type = self.send(:class).to_s
75
+ @webrat_session = Webrat::MechanizeSession.new
76
+ visit @host if @host
17
77
  end
18
78
 
19
79
  # before_tour runs once per tour, before any tests get run
@@ -28,6 +88,10 @@ class Tour
28
88
  def teardown
29
89
  end
30
90
 
91
+ def wait(time)
92
+ sleep time.to_i
93
+ end
94
+
31
95
  # Lists tours in tours folder. If a string is given, filters the
32
96
  # list by that string. If an array of filter strings is given,
33
97
  # returns items that match ANY filter string in the array.
@@ -68,77 +132,16 @@ class Tour
68
132
  send @test
69
133
  teardown
70
134
  end
71
-
135
+
72
136
  protected
73
-
74
- def log(message)
75
- puts "#{Time.now.strftime('%F %H:%M:%S')} Tour ##{@tour_id}: (#{@test}) #{message}"
76
- end
77
-
78
- # given "portal", opens "http://#{@host}/portal". Leading slash is
79
- # optional. "/portal" and "portal" are the same.
80
- def open_site_page(path)
81
- path = path.sub %r{^/}, ""
82
- open_page "http://#{@host}/#{path}"
83
- end
84
137
 
85
- def dump_form
86
- log "Dumping Forms:"
87
- page.forms.each do |form|
88
- puts "Form: #{form.name}"
89
- puts '-' * 20
90
- (form.fields + form.radiobuttons + form.checkboxes + form.file_uploads).each do |field|
91
- puts " #{field.name}"
92
- end
93
- end
138
+ def session
139
+ @session ||= Webrat::MechanizeSession.new
94
140
  end
95
141
 
96
- # True if uri ends with the string given. If a regex is given, it is
97
- # matched instead.
98
- #
99
- # TODO: Refactor me--these were separated out back when Websickle
100
- # was a shared submodule and we couldn't pollute it. Now that it's
101
- # frozen these probably belong there.
102
- def assert_page_uri_matches(uri)
103
- case uri
104
- when String:
105
- raise WebsickleException, "Expected page uri to match String '#{uri}' but did not. It was #{page.uri}" unless page.uri.to_s[-uri.size..-1] == uri
106
- when Regexp:
107
- raise WebsickleException, "Expected page uri to match Regexp '#{uri}' but did not. It was #{page.uri}" unless page.uri.to_s =~ uri
108
- end
109
- log "Page URI ok (#{page.uri} matches: #{uri})"
142
+ def log(message)
143
+ puts "#{Time.now.strftime('%F %H:%M:%S')} Tour ##{@tour_id}: (#{@test}) #{message}"
110
144
  end
111
145
 
112
- # True if page contains (or matches) the given string (or regexp)
113
- #
114
- # TODO: Refactor me--these were separated out back when Websickle
115
- # was a shared submodule and we couldn't pollute it. Now that it's
116
- # frozen these probably belong there.
117
- def assert_page_body_contains(pattern)
118
- case pattern
119
- when String:
120
- raise WebsickleException, "Expected page body to contain String '#{pattern}' but did not. It was #{page.body}" unless page.body.to_s.index(pattern)
121
- when Regexp:
122
- raise WebsickleException, "Expected page body to match Regexp '#{pattern}' but did not. It was #{page.body}" unless page.body.to_s =~ pattern
123
- end
124
- log "Page body ok (matches #{pattern})"
125
- end
126
-
127
-
128
-
129
- # True if page does not contain (or match) the given string (or regexp)
130
- #
131
- # TODO: Refactor me--these were separated out back when Websickle
132
- # was a shared submodule and we couldn't pollute it. Now that it's
133
- # frozen these probably belong there.
134
- def assert_page_body_does_not_contain(pattern)
135
- case pattern
136
- when String:
137
- raise WebsickleException, "Expected page body to not contain String '#{pattern}' but it did. It was #{page.body}" if page.body.to_s.index(pattern)
138
- when Regexp:
139
- raise WebsickleException, "Expected page body to not match Regexp '#{pattern}' but it did. It was #{page.body}" if page.body.to_s =~ pattern
140
- end
141
- log "Page body ok (does not match #{pattern})"
142
- end
143
146
  end
144
147
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbrady-tourbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Brady
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-04-17 00:00:00 -07:00
13
+ date: 2009-07-25 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -53,6 +53,16 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: "0"
55
55
  version:
56
+ - !ruby/object:Gem::Dependency
57
+ name: webrat
58
+ type: :runtime
59
+ version_requirement:
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
56
66
  description: TourBus, a web stress-testing tool that combines complex 'tour' definitions with scalable concurrent testing
57
67
  email: github@shinybit.com
58
68
  executables:
@@ -76,21 +86,11 @@ files:
76
86
  - lib/tour.rb
77
87
  - lib/tour_bus.rb
78
88
  - lib/tour_watch.rb
79
- - lib/web-sickle/init.rb
80
- - lib/web-sickle/lib/assertions.rb
81
- - lib/web-sickle/lib/hash_proxy.rb
82
- - lib/web-sickle/lib/helpers/asp_net.rb
83
- - lib/web-sickle/lib/helpers/table_reader.rb
84
- - lib/web-sickle/lib/make_nokigiri_output_useful.rb
85
- - lib/web-sickle/lib/web_sickle.rb
86
- - lib/web-sickle/spec/lib/helpers/table_reader_spec.rb
87
- - lib/web-sickle/spec/spec_helper.rb
88
- - lib/web-sickle/spec/spec_helpers/mechanize_mock_helper.rb
89
- - lib/web-sickle/spec/web_sickle_spec.rb
90
89
  - README.rdoc
91
90
  - MIT-LICENSE
92
91
  has_rdoc: true
93
92
  homepage: http://github.com/dbrady/tourbus/
93
+ licenses:
94
94
  post_install_message:
95
95
  rdoc_options:
96
96
  - --line-numbers
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  requirements: []
117
117
 
118
118
  rubyforge_project:
119
- rubygems_version: 1.2.0
119
+ rubygems_version: 1.3.5
120
120
  signing_key:
121
121
  specification_version: 2
122
122
  summary: TourBus web stress-testing tool
@@ -1,17 +0,0 @@
1
- require 'rubygems'
2
- gem 'mechanize', ">= 0.7.6"
3
- gem "hpricot", ">= 0.6"
4
- $: << File.join(File.dirname(__FILE__), 'lib')
5
-
6
- require 'hpricot'
7
- require 'mechanize'
8
-
9
- WWW::Mechanize.html_parser = Hpricot
10
-
11
- require 'web_sickle'
12
- require "assertions"
13
- require "hash_proxy"
14
- require "helpers/asp_net"
15
- require "helpers/table_reader"
16
-
17
- Hpricot.buffer_size = 524288
@@ -1,51 +0,0 @@
1
- class WebSickleAssertionException < Exception; end
2
-
3
- module WebSickle::Assertions
4
- def assert_equals(expected, actual, message = nil)
5
- unless(expected == actual)
6
- report_error <<-EOF
7
- Error: Expected
8
- #{expected.inspect}, but got
9
- #{actual.inspect}
10
- #{message}
11
- EOF
12
- end
13
- end
14
-
15
- def assert_select(selector, message)
16
- assert_select_in(@page, selector, message)
17
- end
18
-
19
- def assert_no_select(selector, message)
20
- assert_no_select_in(@page, selector, message)
21
- end
22
-
23
- def assert_select_in(content, selector, message)
24
- report_error("Error: Expected selector #{selector.inspect} to find a page element, but didn't. #{message}") if (content / selector).blank?
25
- end
26
-
27
- def assert_no_select_in(content, selector, message)
28
- report_error("Error: Expected selector #{selector.inspect} to not find a page element, but did. #{message}") unless (content / selector).blank?
29
- end
30
-
31
- def assert_contains(left, right, message = nil)
32
- (right.is_a?(Array) ? right : [right]).each do | item |
33
- report_error("Error: Expected #{left.inspect} to contain #{right.inspect}, but didn't. #{message}") unless left.include?(item)
34
- end
35
- end
36
-
37
- def assert(passes, message = nil)
38
- report_error("Error: expected true, got false. #{message}") unless passes
39
- end
40
-
41
- def assert_link_text(link, text)
42
- case text
43
- when String
44
- assert_equals(link.text, text)
45
- when Regexp
46
- assert(link.text.match(text))
47
- else
48
- raise ArgumentError, "Don't know how to assert an object like #{text.inspect} - expected: Regexp or String"
49
- end
50
- end
51
- end
@@ -1,9 +0,0 @@
1
- class HashProxy
2
- def initialize(options = {})
3
- @set = options[:set]
4
- @get = options[:get]
5
- end
6
-
7
- def [](key); @get && @get.call(key); end
8
- def []=(key, value); @set && @set.call(key, value); end
9
- end
@@ -1,16 +0,0 @@
1
- module WebSickle::Helpers
2
- module AspNet
3
- def asp_net_do_postback(options)
4
- target_element = case
5
- when options[:button]
6
- find_button(options[:button])
7
- when options[:field]
8
- find_field(options[:field])
9
- else
10
- nil
11
- end
12
- @form.fields << WWW::Mechanize::Form::Field.new("__EVENTTARGET", target_element ? target_element.name : "") if target_element
13
- submit_form_button
14
- end
15
- end
16
- end
@@ -1,39 +0,0 @@
1
- module WebSickle::Helpers
2
- class TableReader
3
- attr_reader :headers, :options, :body_rows, :header_row, :extra_rows
4
-
5
- def initialize(element, p_options = {})
6
- @options = {
7
- :row_selectors => [" > tr", "thead > tr", "tbody > tr"],
8
- :header_selector => " > th",
9
- :header_proc => lambda { |th| th.inner_text.gsub(/[\n\s]+/, ' ').strip },
10
- :body_selector => " > td",
11
- :body_proc => lambda { |header, td| td.inner_text.strip },
12
- :header_offset => 0,
13
- :body_offset => 1
14
- }.merge(p_options)
15
- @options[:body_range] ||= options[:body_offset]..-1
16
- raw_rows = options[:row_selectors].map{|row_selector| element / row_selector}.compact.flatten
17
-
18
- @header_row = raw_rows[options[:header_offset]]
19
- @body_rows = raw_rows[options[:body_range]]
20
- @extra_rows = (options[:body_range].last+1)==0 ? [] : raw_rows[(options[:body_range].last+1)..-1]
21
-
22
- @headers = (@header_row / options[:header_selector]).map(&options[:header_proc])
23
- end
24
-
25
- def rows
26
- @rows ||= @body_rows.map do |row|
27
- hash = {}
28
- data_array = (headers).zip(row / options[:body_selector]).each do |column_name, td|
29
- hash[column_name] = options[:body_proc].call(column_name, td)
30
- end
31
- hash
32
- end
33
- end
34
-
35
- def array_to_hash(data, column_names)
36
- column_names.inject({}) {|h,column_name| h[column_name] = data[column_names.index(column_name)]; h }
37
- end
38
- end
39
- end
@@ -1,15 +0,0 @@
1
- # Nokogiri::XML::Element.class_eval do
2
- # def inspect(indent = "")
3
- # breaker = "\n#{indent}"
4
- # if children.length == 0
5
- # %(#{indent}<#{name}#{breaker} #{attributes.map {|k,v| k + '=' + v.inspect} * "#{breaker} "}/>)
6
- # else
7
- # %(#{indent}<#{name} #{attributes.map {|k,v| k + '=' + v.inspect} * " "}>\n#{children.map {|c| c.inspect(indent + ' ') rescue c.class} * "\n"}#{breaker}</#{name}>)
8
- # end
9
- # end
10
- # end
11
- # Nokogiri::XML::Text.class_eval do
12
- # def inspect(indent = "")
13
- # "#{indent}#{text.inspect}"
14
- # end
15
- # end
@@ -1,227 +0,0 @@
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
@@ -1,137 +0,0 @@
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
@@ -1,7 +0,0 @@
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'
@@ -1,12 +0,0 @@
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
@@ -1,50 +0,0 @@
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