honey-do 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|