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.
- data/README.rdoc +16 -10
- data/examples/contact_app/README.rdoc +34 -40
- data/examples/contact_app/contact_app.rb +2 -2
- data/examples/contact_app/tours/simple.rb +12 -13
- data/examples/contact_app/tours/tourbus.yml +1 -1
- data/lib/common.rb +0 -1
- data/lib/runner.rb +4 -1
- data/lib/tour.rb +72 -69
- metadata +14 -14
- data/lib/web-sickle/init.rb +0 -17
- data/lib/web-sickle/lib/assertions.rb +0 -51
- data/lib/web-sickle/lib/hash_proxy.rb +0 -9
- data/lib/web-sickle/lib/helpers/asp_net.rb +0 -16
- data/lib/web-sickle/lib/helpers/table_reader.rb +0 -39
- data/lib/web-sickle/lib/make_nokigiri_output_useful.rb +0 -15
- data/lib/web-sickle/lib/web_sickle.rb +0 -227
- data/lib/web-sickle/spec/lib/helpers/table_reader_spec.rb +0 -137
- data/lib/web-sickle/spec/spec_helper.rb +0 -7
- data/lib/web-sickle/spec/spec_helpers/mechanize_mock_helper.rb +0 -12
- data/lib/web-sickle/spec/web_sickle_spec.rb +0 -50
data/README.rdoc
CHANGED
@@ -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
|
-
*
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
assert_page_uri_matches "/contacts"
|
82
|
-
end
|
78
|
+
def test_home
|
79
|
+
visit "/"
|
80
|
+
assert_contain "If you click this"
|
83
81
|
|
84
|
-
|
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.
|
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
|
-
+
|
93
|
-
matches the given
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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 "/"
|
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
|
-
+
|
124
|
-
submit
|
125
|
-
|
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
|
-
+
|
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
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
data/lib/common.rb
CHANGED
data/lib/runner.rb
CHANGED
@@ -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,
|
37
|
+
rescue TourBusException, WebratError => e
|
35
38
|
log("********** FAILURE IN RUN! **********")
|
36
39
|
log e.message
|
37
40
|
e.backtrace.each do |trace|
|
data/lib/tour.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
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
|
86
|
-
|
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
|
-
|
97
|
-
|
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.
|
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-
|
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.
|
119
|
+
rubygems_version: 1.3.5
|
120
120
|
signing_key:
|
121
121
|
specification_version: 2
|
122
122
|
summary: TourBus web stress-testing tool
|
data/lib/web-sickle/init.rb
DELETED
@@ -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,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,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
|