honey-do 0.5.0

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/COPYING ADDED
@@ -0,0 +1,11 @@
1
+ Copyright (c) 2008 Tim Camper
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing,
8
+ software distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+
11
+ See the License for the specific language governing permissions and limitations under the License.
data/README ADDED
@@ -0,0 +1,120 @@
1
+ =Just Give it a List!
2
+
3
+ ====Collection-driven, commandless Selenium
4
+ HoneyDo is a small Ruby/Javascript library that supports collection-driven browser automation.
5
+ Currently implemented as a Selenium RC user extension, it provides a simple, consistent interface to any list of 1 or more HTML form elements.
6
+
7
+ Basically, you set one or many values, click one or many controls, or read the whole form with a single call, always using the same sweet syntax.
8
+
9
+ ==When would I use this?
10
+
11
+ ====When you have lots of testing to do on:
12
+ * many forms
13
+ * large forms
14
+ * complex forms
15
+ * dynamic forms
16
+ HoneyDo was born of the need to test a large, input-heavy app with over 350 Oracle tables at the backend and over 100 input screens in the front.
17
+ The business domain and UI workflows are complex, so the tools for making the functional tests needed to be as lightweight and flexible as possible.
18
+ Things like GUI maps and screen driver objects are heavyweight, rigid, and brittle: the amount of maintenance they require keeps you from getting your tests right.
19
+
20
+ ====When data matters, but widgets don't
21
+ In testing app functionality through the UI, it often doesn't make a difference if you _type_, _select_, _check_, or _click_: you just need to get values to the server.
22
+
23
+ ==Examples
24
+ ====With a SeleniumDriver . . .
25
+ browser = Selenium::SeleniumDriver.new('localhost', 44...
26
+ ====Click things
27
+ # submit your form
28
+ browser.set_form_values(:saveButton)
29
+
30
+ # toggle a bunch of checkboxes and submit
31
+ browser.set_form_values( [:include_documents, :calculate_fee, :require_guarantor_address, :fasb_13_ok, :120_day_rule, :UPDATE] )
32
+
33
+ ====Set things
34
+ browser.set_form_values(:user_name => "pprubens", :password => "hy!Bosch53xy")
35
+
36
+ browser.set_form_values(:state => "TN", :street_1 => "9113 Foo St.", :street_2 => "4D", :city => "Erlewine", :postal => 99999, :same_as_shipping => true)
37
+
38
+ ====Read things
39
+ default_values = browser.get_form_values("newProprietorshipForm")
40
+ assert_equal({:state => "AL",
41
+ :typeOfBusiness => "Please Select",
42
+ :taxjurisdiction => "Federal",
43
+ :corporation => "on",
44
+ :submitButton => "Save",
45
+ :cancel => "Cancel"}, default_values, "only default fields populated?")
46
+
47
+ visible_fields = browser.get_form_elements("newProprietorshipForm").split(',')
48
+ assert_equal(visible_fields.size - default_values.size, 17, "count of unset fields on initial page load")
49
+
50
+ ==How does it work?
51
+ The key to HoneyDo#set_form_values is that each HTML <tt>form</tt> element knows its own type. If we identify the element, the code knows what to do.
52
+
53
+ For each element in our input list:
54
+ ====1 - find the element
55
+ The <tt>form.elements</tt> collection is a JavaScript associative array, so we can use either its array index or its <tt>name</tt> attribute.
56
+ For a generic js example, these will all have the same effect if <tt>selectDealer</tt> is the 3rd element:
57
+ element = myForm.elements['selectDealer'];
58
+ element = myForm.elements[2];
59
+ element = myForm.elements['2'];
60
+
61
+ ====2 - get its type
62
+ element.type
63
+ ====3 - set the value (or call <tt>click()</tt>)
64
+ The +type+ could be:
65
+
66
+ settable: <tt>(text, textarea, password)</tt>:: The +value+ attribute is set
67
+ selectable: <tt>(select-one, select-mulitple)</tt>:: The +option+ index is found and the +selected+ attribute is set
68
+ clickable:<tt>(button, submit, reset, radio)</tt>:: <tt>click()</tt> is called.
69
+ checkbox: <tt>(checkbox)</tt>:: the +checked+ attribute is set or <tt>click()</tt> is called
70
+
71
+ When <tt>element.type</tt> has no value, then +element+ is itself a collection of other elements, i.e., a group of checkboxes or radio buttons with the same +name+.
72
+ In this case <tt>click()</tt> is called on the correct item in the goup.
73
+
74
+ ====4 - fire event handlers
75
+ Any or all of these are fired, if present:
76
+ onclick()
77
+ onchange()
78
+ onblur()
79
+
80
+ ====Ajax
81
+ Nothing explicit is done to wait for Ajax calls. On HoneyDo's home app, we find that in IE the built-in <tt>retry</tt> is enough to handle the occasional glitch.
82
+ In Firefox, multiple call mode using the <tt>:one_field_at_a_time</tt> option is required, but does in fact work.
83
+ We've tried the <i>prototype.js</i> <tt>Ajax.activeRequestCount</tt> trick, but couldn't figure out how get a reference to the right <tt>Ajax</tt> instance.
84
+
85
+ I make no guarantees about how this will work with _your_ Ajax calls, but I'd suggest trying <tt>:one_field_at_a_time => true</tt> for your Ajax pages and just leaving it alone otherwise.
86
+ If you need an explicit _wait_ and have the solution, JavaScript <tt>Form_.prototype.set_value()</tt> would be the place to call it from.
87
+
88
+ ==Installation[link:files/doc/INSTALLATION.html]
89
+ There's a manual step[link:files/doc/INSTALLATION.html].
90
+
91
+ ==The Implementation
92
+ Selenium user extensions that aren't implemented as methods on the <tt>Selenium</tt> JavaScript prototype object might be odd.
93
+ However, some of these methods require pre- or post-processing in Ruby, so I couldn't think of any other way to do it.
94
+
95
+ I'm always open to better solutions. I'm more interested in propagating the list-driven technique wherever it's applicable than in preserving any particular encoding of it.
96
+
97
+ Blessings upon whoever came up with <tt>SeleniumDriver.get_eval()</tt>!
98
+
99
+ ==The Tests[link:/classes/TestGeneralInputOptions.html] ...
100
+ ... are the *documentation*. They detail the features and limitations.
101
+ Run[link:/files/doc/running_the_tests_rdoc.html] them and read[link:/classes/TestGeneralInputOptions.html] them.
102
+
103
+ ====See running_the_tests[link:/files/doc/running_the_tests_rdoc.html]
104
+
105
+ ==Why be collection-driven?
106
+
107
+ ====More testing, less code
108
+ As a functional test automator and automation lead, I want my scripts, scripters, and self to do more of the testing work that matters,
109
+ in less time, with less code to distract from finding bugs.
110
+
111
+ At run time, scripts go faster[link:/classes/TestFastAndPretty.html] when all of the fields are set or read in one go, rather than with an individual Selenium call for each field.
112
+ At development and maintenance time, the code is streamlined considerably by removing the usually unnecessary layer of "action" commands. And the
113
+ scripters' work goes faster when there is only one API to learn, as opposed to many different ones for all of the different widget types.
114
+
115
+ ====User emulation is waste
116
+ Named actions (_click_, _select_, _toggle_, etc) that require us to identify the type of control being called and to specify the arguments to each command differently derive from
117
+ the assumption that functional test automation implies or requires end-user emulation. Emulating human users and testing app functionality are two
118
+ different programming problems, and while user emulation is certainly possible it rarely adds anything but clutter, confusion, and complexity to the
119
+ functional testing effort. Unless there is an explicit testing need to make assertions about the mechanical details of interacting with widgets in
120
+ type-specific ways, then we are better off without named actions.
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require 'rake/rdoctask'
2
+ require 'rake/testtask'
3
+ require 'rake/gempackagetask'
4
+ require 'open-uri'
5
+
6
+ desc "run all tests"
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << 'functional_testcase' << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ t.verbose = false
11
+ end
12
+ task :test => [:selenium, :webrick]
13
+
14
+ desc "verify selenium is running"
15
+ task :selenium do
16
+ error = nil
17
+ begin
18
+ open("http://localhost:4444")
19
+ rescue Exception => ex
20
+ error = ex.message
21
+ end
22
+ raise RuntimeError.new("SELENIUM IS NOT RUNNING:\n#{error}") if error !~ /403 /
23
+ end
24
+
25
+ desc "verify webrick demo server is running"
26
+ task :webrick do
27
+ error = nil
28
+ begin
29
+ open("http://localhost:8000")
30
+ rescue Exception => ex
31
+ error = ex.message
32
+ end
33
+ raise RuntimeError.new("WEBRICK SERVER IS NOT RUNNING:\n#{error}") if error
34
+ end
35
+
36
+ RDOC_OPTS = ["--all" , "--quiet" , "--line-numbers" , "--inline-source",
37
+ "--main", "README",
38
+ "--title", "Honey-Do: Just give it a list!"]
39
+ XTRA_RDOC = %w{README COPYING js/user-extensions.js doc/INSTALLATION doc/running_the_tests.rdoc}
40
+
41
+ Rake::RDocTask.new do |rd|
42
+ rd.rdoc_dir = "doc/rdoc"
43
+ rd.rdoc_files.include("**/*.rb")
44
+ rd.rdoc_files.add(XTRA_RDOC)
45
+ rd.options = RDOC_OPTS
46
+ end
47
+
48
+ spec = Gem::Specification.new do |s|
49
+ s.name = 'honey-do'
50
+ s.version = '0.5.0'
51
+ s.rubyforge_project = s.name
52
+
53
+ s.platform = Gem::Platform::RUBY
54
+ s.has_rdoc = true
55
+ s.extra_rdoc_files = XTRA_RDOC + FileList["test/*.rb", "functional_testcase/*.rb", "demo/**/*.rb"].to_a
56
+ s.rdoc_options += RDOC_OPTS
57
+ s.summary = "collection driven, commandless form handling for Selenium"
58
+ s.description = s.summary
59
+ s.author = "Tim Camper"
60
+ s.email = 'twcamper@thoughtworks.com'
61
+ s.homepage = 'http://honey-do.rubyforge.org/'
62
+
63
+ s.add_dependency('Selenium', '>= 1.0.1')
64
+ s.required_ruby_version = '>= 1.8.2'
65
+
66
+ s.files = %w(COPYING README Rakefile) +
67
+ FileList["lib/*.rb", "test/*.rb", "demo/**/*", "functional_testcase/*.rb", "bin/*"].to_a
68
+
69
+ s.require_path = "lib"
70
+ end
71
+
72
+ Rake::GemPackageTask.new(spec) do |p|
73
+ p.need_zip = true
74
+ p.need_tar = true
75
+ p.gem_spec = spec
76
+ end
@@ -0,0 +1,4 @@
1
+ @echo off
2
+ REM SAMPLE windows command line
3
+ REM your paths, Sel version and options may vary
4
+ java -jar ..\..\selenium-remote-control-0.9.2\server\selenium-server.jar -multiWindow -userExtensions ..\js\user-extensions.js
@@ -0,0 +1,3 @@
1
+ # SAMPLE command line
2
+ # your paths, Sel version and options may vary
3
+ java -jar ../../selenium-remote-control-0.9.2/server/selenium-server-ff3.jar -multiWindow -userExtensions ../js/user-extensions.js
@@ -0,0 +1,112 @@
1
+ <html>
2
+ <head>
3
+ <title>HoneyDo Demo Form: Just give it a list!</title>
4
+ <script language="JavaScript" src="everything.js"></script>
5
+ </head>
6
+ <body>
7
+ <!-- A one-of-everything HTML form:
8
+ after David Flanagan, Chapter 18 of "JavaScript: The Definitive Guide", O'Reilly, 5th edition
9
+ -->
10
+ <div align="center">
11
+ <form name="everything" method="POST" action="../display">
12
+ <table border="3" bgcolor="beige" cellspacing="1" cellpadding="4">
13
+ <tr>
14
+ <td colspan="4"><h4>All Possible Form Elements</h4></td>
15
+ </tr>
16
+ <tr>
17
+ <td>Username:<br>[1]<input type="text" name="username" size="15"></td>
18
+ <td>Password:<br>[2]<input type="password" name="password" size="15"></td>
19
+ <td rowspan="4">Input Events[3]<br>
20
+ <textarea name="event_reports_area" rows="20" cols="40"></textarea></td>
21
+ <td rowspan="4" align="center" valign="center">
22
+ [10]<input type="button" value="Clear" name="clearButton"><br>
23
+ [11]<input type="submit" value="Submit" name="submitButton"><br>
24
+ [12]<input type="reset" value="Reset" name="resetButton"><br>
25
+ [13]<input type="hidden" value="you can't see this unless you're looking at the source" name="justHiding"><br></td>
26
+ </tr>
27
+ <tr>
28
+ <td colspan="2">
29
+ Filename: [4]<input type="file" name="file" size="15"> </td>
30
+ </tr>
31
+ <tr>
32
+ <td><p>My Computer Peripherals:</p>
33
+ [5]<input type="checkbox" name="extras" value="burner">DVD Writer<br>
34
+ [5]<input type="checkbox" name="extras" value="printer">Printer<br>
35
+ [5]<input type="checkbox" name="extras" value="card">Card Reader<br>
36
+ <p>My Utilities:</p>
37
+ [6]<input type="checkbox" name="disk manager">Disk Manager<br>
38
+ [6]<input type="checkbox" name="hackzall">Firewall Drill Pro<br>
39
+ [6]<input type="checkbox" name="a-clock.exe">AtomSynch<br>
40
+ [6]<input type="checkbox" name="version control client">Sottoversione 1.4.3<br></td>
41
+ <td><p>My Web Browser:</p>
42
+ [7]<input type="radio" name="browser" value="ff">Firefox<br>
43
+ [7]<input type="radio" name="browser" value="ie">Internet Explorer<br>
44
+ [7]<input type="radio" name="browser" value="safari">Safari<br>
45
+ [7]<input type="radio" name="browser" value="opera">Opera<br>
46
+ [7]<input type="radio" name="browser" value="other browser">other<br></td>
47
+ </tr>
48
+ <tr>
49
+ <td>My Hobbies:[8]<br>
50
+ <select multiple="multiple" name="hobbies" size="5">
51
+ <option value="programming">Hacking JavaScript
52
+ <option value="surfing">Surfing the Web
53
+ <option value="caffeine">Drinking Coffee
54
+ <option value="annoying">Annoying my friends
55
+ <option value="zipping">Zipping around on my scooter
56
+ </select></td>
57
+ <td align="center" valign="center">My Favorite Color:<br>[9]
58
+ <select name="color">
59
+ <option value="0">
60
+ <option value="red">Red
61
+ <option value="orange">Orange
62
+ <option value="yellow">Yellow
63
+ <option value="green">Green
64
+ <option value="blue">Blue
65
+ <option value="indigo">Indigo
66
+ <option value="violet">Violet
67
+ <option value="black">Black
68
+ <option value="brown">Brown
69
+ <option value="white">White
70
+ <option value="grey">Grey
71
+ <option value="light grey">Light Grey
72
+ <option value="flat eggshell white">Off White
73
+ <option value="taupe">Taupe
74
+ <option value="beige">Beige
75
+ <option value="10256">Mauve
76
+ <option value="peach">Peach
77
+ <option value="purple">Purple
78
+ <option value="maroon">Maroon
79
+ <option value="fuchsia">Fuchsia
80
+ <option value="silver">Silver
81
+ <option value="gold">Gold
82
+ <option value="zinc">Zinc
83
+ </select></td>
84
+ </tr>
85
+ </table>
86
+ </form>
87
+ <table border="9" bgcolor="beige" cellspacing="1" cellpadding="4">
88
+ <tr>
89
+ <td colspan="7"><h4>Form Element Types</h4></td>
90
+ </tr>
91
+ <tr>
92
+ <td>[1] text</td> <td>[2] password</td> <td>[3] textarea</td>
93
+ <td>[4] file (upload)</td> <td>[5] checkbox (group)</td> <td colspan="2">[6] checkbox (independent)</td>
94
+ </tr>
95
+ <tr>
96
+ <td>[7] radio</td> <td>[8] select (multiple)</td>
97
+ <td>[9] select</td> <td>[10] button</td>
98
+ <td>[11] submit</td> <td>[12] reset</td><td>[13] hidden</td>
99
+ </tr>
100
+ </table>
101
+ <form name="events_button_form">
102
+ <table border="3" bgcolor="beige" cellspacing="1" cellpadding="4">
103
+ <tr><td> <h4>Turn on Event Handlers</h4></td></tr>
104
+ <tr><td>
105
+ <input type="button" value="Enable Handlers" name="enableEventsButton" onclick="addhandlers(document.everything)">
106
+ <input type="button" value="Disable Handlers" name="disableEventsButton" onclick="removehandlers(document.everything)">
107
+ </td>
108
+ </tr>
109
+ </table>
110
+ </form>
111
+ </div>
112
+ </body>
@@ -0,0 +1,55 @@
1
+ /*
2
+ functions for "A one-of-everything HTML form":
3
+ from David Flanagan, Chapter 18 of "JavaScript: The Definitive Guide", O'Reilly, 5th edition
4
+ */
5
+
6
+ // This generic function appends details of an event to the big Textarea
7
+ // element in the form above. It is called from various event handlers.
8
+ function report(element, event) {
9
+ if (selectable(element.type)) {
10
+ value = "";
11
+ for(var i = 0; i < element.options.length; i++)
12
+ if (element.options[i].selected)
13
+ value += element.options[i].value + " ";
14
+ }
15
+ else if (element.type == "textarea") value = "...";
16
+ else value = element.value;
17
+
18
+ var msg = event + ":" + element.name + ' (' + value + ')\n';
19
+ var t = element.form.event_reports_area;
20
+ t.value = t.value + msg;
21
+
22
+ }
23
+ // This function adds a bunch of event handlers to every element in a form.
24
+ // Note that the event handlers all call report().
25
+
26
+ // We are defining event handlers by assigning functions to the properties
27
+ // of JavaScript objects rather than by assigning strings to the attributes
28
+ // of HTML elements.
29
+ function addhandlers(f) {
30
+ for(var i = 0; i < f.elements.length; i++) {
31
+ var e = f.elements[i];
32
+ e.onclick = function() {report(this, 'Click');}
33
+ e.onchange = function() {report(this, 'Change');}
34
+ e.onfocus = function() {report(this, 'Focus');}
35
+ e.onblur = function() {report(this, 'Blur');}
36
+ }
37
+
38
+ // Special case event handlers for the 3 buttons
39
+ f.clearButton.onclick = function() { this.form.event_reports_area.value = ''; report(this, 'Click'); }
40
+ f.submitButton.onclick = function() { report(this, 'Click'); return false;}
41
+ f.resetButton.onclick = function() { this.form.reset(); report(this, 'Click'); return false; }
42
+ }
43
+
44
+ function removehandlers(f) {
45
+ for(var i = 0; i < f.elements.length; i++) {
46
+ var e = f.elements[i];
47
+ e.onchange = null;
48
+ e.onfocus = null;
49
+ e.onblur = null;
50
+ if (e.name == "clearButton") continue;
51
+ e.onclick = null;
52
+ }
53
+ }
54
+
55
+ function selectable(type) { return (/^select/i).test(type);}
@@ -0,0 +1,58 @@
1
+ require 'webrick'
2
+
3
+ class HoneyDoDemoServlet < WEBrick::HTTPServlet::AbstractServlet
4
+
5
+ def do_POST(request, response)
6
+ status, content_type, body = format_form_values(request)
7
+
8
+ response.status = status
9
+ response['Content-Type'] = content_type
10
+ response.body = body
11
+ end
12
+
13
+ # makes a document with a table of submitted form values from any POST request body.
14
+ def format_form_values(request)
15
+ html = "<html><head><title>#{Time.now.strftime('%A %B %d, %H:%M:%S')}</title></head>"
16
+ html += "\n<body><table border='3' bgcolor='beige' cellpadding='3' cellspacing = '2' id='form_values_table'>"
17
+ html += "\n<h2>Submitted from: &nbsp;<span>#{request.header['referer'].first}</span></h2>"
18
+
19
+ parse_query(request.body).sort.collect do | pair |
20
+ html += write_row(*pair)
21
+ end
22
+
23
+ html += "\n</table></body></html>"
24
+
25
+ return 200, "text/html", html
26
+ end
27
+
28
+ # <pre> the value cell to get newlines
29
+ def write_row(name, value)
30
+ "\n<tr><td>#{name}:&nbsp;</td><td id='#{name}'><pre>#{value}</pre></td></tr>"
31
+ end
32
+
33
+ # For some reason WEBrick doesn't append multiple values for checkboxes and multi-selects, thus this hack.
34
+ def parse_query(body)
35
+ query = {}
36
+ body.split(/[;&]/).each do |form_item|
37
+ key, value = form_item.split(/=/)
38
+ key = WEBrick::HTTPUtils::unescape_form(key)
39
+ value = WEBrick::HTTPUtils::unescape_form(value.to_s)
40
+ if query[key]
41
+ query[key] += "|" + value
42
+ else
43
+ query[key] = value
44
+ end
45
+ end
46
+ return query
47
+ end
48
+ end
49
+
50
+ if $0 == __FILE__ then
51
+ server = WEBrick::HTTPServer.new(:Port => 8000,
52
+ :Host => 'localhost',
53
+ :DocumentRoot => Dir::pwd + '/page')
54
+
55
+ server.mount "/display", HoneyDoDemoServlet
56
+ trap "INT" do server.shutdown end
57
+ server.start
58
+ end
data/doc/INSTALLATION ADDED
@@ -0,0 +1,20 @@
1
+ === 1 - Have SeleniumRC[http://selenium-rc.seleniumhq.org/]
2
+ === 2 - Get the gem
3
+ gem install honey-do
4
+ === 3 - Move user-extensions.js
5
+ 1. If you're already using a <tt>user-extensions.js</tt>, then merge the contents of <tt>honey-do/js/user-extensions.js</tt> in with yours.
6
+ 1. Otherwise, move or copy <tt>honey-do/js/user-extensions.js</tt> to a folder convenient to your selenium proxy server.
7
+ 1. Start your selenium proxy with the <tt>-userExtensions</tt> option. For example, if your working directory contains both <tt>user-extensions.js</tt> and the <tt>selenium-remote-control-0.9.2</tt> folder:
8
+
9
+ java -jar selenium-remote-control-0.9.2\server\selenium-server.jar -userExtensions user-extensions.js
10
+
11
+ See <tt>honey-do/bin</tt> for other cmd line examples.
12
+
13
+ See also SeleniumRC command line reference[http://selenium-rc.seleniumhq.org/options.html]
14
+ === 4 - +require+ / +include+
15
+ require 'honey_do'
16
+ require 'selenium'
17
+
18
+ <b>Nothing to +include+</b>.
19
+
20
+ See <tt>functional_testcase/functional_testcase.rb</tt> and <tt>test/test_master.rb</tt> for examples.
@@ -0,0 +1,81 @@
1
+ =Running The Tests
2
+
3
+ ===Once you've installed[link:/files/doc/INSTALLATION.html]
4
+
5
+ ====1 - Start your selenium proxy server
6
+ Make sure the honey_do version of <i>user-extensions.js</i> gets loaded. An example, minimal command line would be:
7
+ java -jar .\selenium-remote-control-0.9.2\server\selenium-server.jar -userExtensions user-extensions.js
8
+
9
+ See also <i>honey-do/bin</i> for other examples.
10
+
11
+ ====2 - Start honey-do/demo/webrick-servlet.rb
12
+ ruby webrick-servlet.rb # you don't need to say 'ruby' on win32
13
+
14
+ You'll then have a webserver listening at http://localhost:8000
15
+
16
+ ====3 - Run honey-do/Rakefile as
17
+ honey-do/rake test
18
+
19
+ This gathers and runs all files ending in <i>_test.rb</i>, and an extended version of Test::Unit::TestCase handles starting and stopping the Selenium driver.
20
+ At the end, it will print to <tt>stdout</tt> a list of tests run, with run times. A few of the tests print things as part of their illustrative mission.
21
+
22
+ Run just the tests in a given file at the command line:
23
+ ruby fast_and_pretty_test.rb # no need for 'ruby' on win32
24
+
25
+ Run individual tests or sub-suites at the command line like this:
26
+ ruby checkbox_test.rb test_check_multiple_independents
27
+ ruby checkbox_test.rb test_check_uncheck test_check_multiple_in_group test_check_multiple_independents
28
+
29
+ ====The Demo Page
30
+ <tt>everything.html</tt> is a contrived example form, with all of the possible element types represented (thanks to David Flanagan).
31
+ There are event handlers to prove that HoneyDo fires them, but there is no Ajax.
32
+
33
+ ====The Servlet
34
+ HoneyDoDemoServlet generates a display page showing whatever form data was submitted, but only for POST requests. (HoneyDo itself doesn't know or care if a form is sent via GET or POST, however.)
35
+
36
+ ====Changing the Browser
37
+ In <i>functional_testcase/functional_testcase.rb</i>, see the <tt>browser()</tt> method on Test::Unit::TestCase.
38
+
39
+ Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://localhost:8000", 60000)
40
+
41
+ Use <tt>"*iexplore"</tt> for that MS thing.
42
+
43
+ =Sample Output
44
+
45
+ C:\honey-do>rake test
46
+ (in C:/honey-do)
47
+ c:/ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb
48
+ Loaded suite c:/ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
49
+ Started
50
+ .....................................................
51
+ TEST OUTPUT:
52
+ *** *** TEST_HONEY_DO_TIME *** ***
53
+ honey_do_input, setting whole form 10 times: 0.608999967575073
54
+
55
+ *** *** TEST_ONE_FIELD_AT_A_TIME_HONEY_DO_TIME *** ***
56
+ one_field_at_a_time_input, setting whole form 10 times: 3.8289999961853
57
+
58
+ *** *** TEST_SELENIUM_TIME *** ***
59
+ selenium_input, setting whole form 10 times: 3.53099989891052
60
+ .
61
+ .
62
+ .
63
+
64
+ TESTS RUN:
65
+ 1 - test_check_multiple_in_group(TestCheckboxFeatures) - 0.328
66
+ 2 - test_check_multiple_independents(TestCheckboxFeatures) - 0.25
67
+ .
68
+ .
69
+ .
70
+ 18 - test_honey_do_time(TestFastAndPretty) - 0.766
71
+ 19 - test_one_field_at_a_time_honey_do_time(TestFastAndPretty) - 3.938
72
+ 20 - test_selenium_time(TestFastAndPretty) - 3.703
73
+ .
74
+ .
75
+ .
76
+ 53 - test_triple_colon_is_input_delimiter_with_mulitiple_fields(TestTextareaInput) - 0.265
77
+ 54 - test_triple_colon_is_input_delimiter_with_single_field(TestTextareaInput) - 0.25
78
+ .
79
+ Finished in 34.797 seconds.
80
+
81
+ 54 tests, 131 assertions, 0 failures, 0 errors
@@ -0,0 +1,99 @@
1
+ # Extend Test::Unit::Testcase
2
+ # Sel. session is started in setup() of first testcase, and stopped in teardown() of last
3
+ require 'test/unit'
4
+ require 'selenium'
5
+
6
+ module Test::Unit
7
+ class TestCase
8
+ @@total_test_count = nil
9
+ def TestCase.total_test_count
10
+ @@total_test_count ||= 0
11
+ end
12
+
13
+ def TestCase.add_to_total_test_count(count_to_add)
14
+ @@total_test_count = total_test_count + count_to_add
15
+ end
16
+
17
+ # command line arguments which could be individual test method names
18
+ @@args = Array.new(ARGV)
19
+
20
+ # list of 1 or more test method names to follow a test file name
21
+ def TestCase.cmd_line_tests
22
+ @@args.select {|arg| arg =~ /^test_/}
23
+ end
24
+
25
+ # for each TestCase.suite
26
+ def self.all_test_method_names
27
+ @all_test_method_names ||= public_instance_methods(true).select {|m| m =~ /^test_/}
28
+ end
29
+
30
+ # gets tests from TestCase.suite or from the command line
31
+ def self.tests
32
+ tests = all_test_method_names
33
+ # intersection of arrays: remove any tests not on the command line
34
+ return (tests & cmd_line_tests) unless cmd_line_tests.empty?
35
+
36
+ return tests
37
+ end
38
+
39
+ # * collects test method names into suite object
40
+ # * accumulates total test count from mulitple suites
41
+ def self.suite
42
+ suite = TestSuite.new(name)
43
+ tests.sort.each do |test|
44
+ catch(:invalid_test) do
45
+ suite << new(test)
46
+ end
47
+ end
48
+
49
+ add_to_total_test_count(suite.tests.size) unless suite.empty?
50
+ return suite
51
+ end
52
+
53
+ def start_time()
54
+ @start_time ||= Time.now()
55
+ end
56
+
57
+ def elapsed()
58
+ Time.now() - start_time
59
+ end
60
+
61
+ @@browser = nil
62
+ # <tt>SeleniumDriver</tt> reference
63
+ def browser
64
+ @@browser ||= Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://localhost:8000", 60000)
65
+ end
66
+
67
+ def selenium_driver_started?
68
+ browser.inspect.include?("@session_id")
69
+ end
70
+
71
+ # starts <tt>SeleniumDriver</tt> if necessary
72
+ def setup()
73
+ browser.start unless selenium_driver_started?
74
+ start_time()
75
+ end
76
+
77
+ @@print_buffer = []
78
+ @@test_names = []
79
+ # stops <tt>SeleniumDriver</tt> after the last test in the last suite
80
+ def teardown
81
+ @@test_names << "#{name} - #{elapsed}"
82
+
83
+ if run_count.eql?(TestCase.total_test_count)
84
+ browser.stop()
85
+ unless @@print_buffer.empty?
86
+ puts "\nTEST OUTPUT:"
87
+ puts @@print_buffer
88
+ end
89
+ puts "\nTESTS RUN:"
90
+ @@test_names.each_with_index {|test_name, i| puts "#{i + 1} - #{test_name}"}
91
+ end
92
+ end
93
+
94
+ def run_count
95
+ @_result.run_count + 1
96
+ end
97
+
98
+ end
99
+ end