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 +11 -0
- data/README +120 -0
- data/Rakefile +76 -0
- data/bin/start_selenium_proxy.bat +4 -0
- data/bin/start_selenium_proxy.sh +3 -0
- data/demo/display/webrick_servlet_needs_this_folder.txt +0 -0
- data/demo/page/everything.html +112 -0
- data/demo/page/everything.js +55 -0
- data/demo/webrick-servlet.rb +58 -0
- data/doc/INSTALLATION +20 -0
- data/doc/running_the_tests.rdoc +81 -0
- data/functional_testcase/functional_testcase.rb +99 -0
- data/js/user-extensions.js +359 -0
- data/lib/hash.rb +28 -0
- data/lib/honey_do.rb +257 -0
- data/lib/symbol.rb +6 -0
- data/test/checkbox_test.rb +118 -0
- data/test/events_test.rb +75 -0
- data/test/fast_and_pretty_test.rb +66 -0
- data/test/file_upload_test.rb +34 -0
- data/test/general_form_test.rb +205 -0
- data/test/hidden_and_disabled_field_test.rb +51 -0
- data/test/radio_button_test.rb +42 -0
- data/test/select_list_test.rb +155 -0
- data/test/submit_test.rb +82 -0
- data/test/test_master.rb +18 -0
- data/test/textarea_test.rb +92 -0
- metadata +114 -0
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
|
File without changes
|
@@ -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: <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}: </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
|