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
@@ -0,0 +1,359 @@
|
|
1
|
+
/* reference to Selenium's browserbot window -- use this to access app window, else you'll get sel runner window*/
|
2
|
+
function aut_window(page) {return page.browserbot.getCurrentWindow();}
|
3
|
+
|
4
|
+
/* document reference in our frame or window of interest */
|
5
|
+
function aut_document(page) {return aut_window(page).document;}
|
6
|
+
|
7
|
+
/* Form_ class */
|
8
|
+
// Form_ class constructor: provides reference to aut document and either a specified form or the first one by default
|
9
|
+
// form can be identified by name or index
|
10
|
+
// if using name, caller responsible for wrapping in single quotes: e.g. Form_(this, "'searchForm'")
|
11
|
+
function Form_(page, form_name_or_index) {
|
12
|
+
form_identifier = form_name_or_index || 0; // default to first in collection
|
13
|
+
this.doc = aut_document(page);
|
14
|
+
this.form = this.doc.forms[form_identifier];
|
15
|
+
}
|
16
|
+
|
17
|
+
Form_.FIELD_DELIMITER = '|'
|
18
|
+
Form_.PAIR_DELIMITER = ':::'
|
19
|
+
|
20
|
+
Form_.prototype.set_values = function(big_string, ok_if_not_found){
|
21
|
+
var pairs = big_string.split(Form_.FIELD_DELIMITER), pair = [], error_values = '';
|
22
|
+
|
23
|
+
for(var i = 0; i < pairs.length; i++){
|
24
|
+
pair = pairs[i].split(Form_.PAIR_DELIMITER);
|
25
|
+
error_values += this.set_value(pair[0], pair[1], ok_if_not_found);
|
26
|
+
}
|
27
|
+
return error_values;
|
28
|
+
}
|
29
|
+
|
30
|
+
Form_.prototype.set_value = function(name, value, ok_if_not_found){
|
31
|
+
var error_text = '', e = null;
|
32
|
+
var selectable = /(select-one|select-multiple)/i;
|
33
|
+
var settable = /(text|textarea|password)/i;
|
34
|
+
var clickable = /(button|submit|reset|radio|image)/i;
|
35
|
+
try {
|
36
|
+
// if you wanted to do something to explicitly wait for Ajax, you would do it here, before the element lookup
|
37
|
+
e = this.form.elements[name];
|
38
|
+
|
39
|
+
// complain if not found
|
40
|
+
if (e == null && ok_if_not_found == null) throw new Error("Form_.set_value: element '" + name + "' not found");
|
41
|
+
|
42
|
+
if (ok_if_not_found && e == null) return;
|
43
|
+
|
44
|
+
// query type to infer action
|
45
|
+
if (e.type == undefined) Form_.grouped_item(e, value).click();
|
46
|
+
if (settable.test(e.type)) e.value = value;
|
47
|
+
if (selectable.test(e.type)) Form_.select_option(e, value);
|
48
|
+
if ((/checkbox/i).test(e.type)) Form_.set_checkbox(e, value);
|
49
|
+
if (clickable.test(e.type)) e.click();
|
50
|
+
|
51
|
+
// fire any event handlers synthetically.
|
52
|
+
if (this.doc.createEventObject) Form_.fire_ie_event_handlers(e, this.doc.createEventObject());
|
53
|
+
if (this.doc.createEvent) Form_.fire_ff_event_handlers(e, this.doc);
|
54
|
+
}
|
55
|
+
catch(ex) { error_text = name + Form_.PAIR_DELIMITER + value + Form_.FIELD_DELIMITER + ex.message + '\n';}
|
56
|
+
finally {return error_text;}
|
57
|
+
}
|
58
|
+
|
59
|
+
Form_.fire_event_handlers = function(e, obj, method){
|
60
|
+
var handlers = ['onclick', 'onchange', 'onblur'];
|
61
|
+
|
62
|
+
for (var h = 0; h < handlers.length; h++)
|
63
|
+
if (e[handlers[h]]) {method(e, obj, handlers[h]);}
|
64
|
+
}
|
65
|
+
|
66
|
+
Form_.fire_ie_event_handlers = function(e, event_obj){
|
67
|
+
Form_.fire_event_handlers(e, event_obj, function(e, event_obj, handler) { e.fireEvent(handler, event_obj)})
|
68
|
+
}
|
69
|
+
|
70
|
+
Form_.fire_ff_event_handlers = function(e, doc){
|
71
|
+
Form_.fire_event_handlers(e, doc, function(e, doc, handler) {Form_.fire_ff_event(e, doc, handler)})
|
72
|
+
}
|
73
|
+
|
74
|
+
Form_.fire_ff_event = function(element, doc, handler) {
|
75
|
+
event = doc.createEvent("Events");// DOM initEvent requires a new object each time
|
76
|
+
event.initEvent(handler.replace(/on/,'').toLowerCase(), true, false);
|
77
|
+
element.dispatchEvent(event);
|
78
|
+
}
|
79
|
+
|
80
|
+
// find particular radio button or checkbox from group sharing common 'name' attribute
|
81
|
+
Form_.grouped_item = function(e, unique_value) {
|
82
|
+
var checkable = /(radio|checkbox)/i;
|
83
|
+
|
84
|
+
if (e.length == undefined) throw new Error("Form_.grouped_item: element type and length properties undefined");
|
85
|
+
for (var i = 0; i < e.length; i++)
|
86
|
+
if (checkable.test(e[i].type)) {
|
87
|
+
if (i == unique_value || e[i].value == unique_value) return e[i];
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
Form_.select_option = function(e, value) {
|
92
|
+
var i = Form_.option_index(e, value);
|
93
|
+
e.options[i].selected = true;
|
94
|
+
}
|
95
|
+
|
96
|
+
Form_.option_index = function(e, in_value){
|
97
|
+
if (isNaN(parseInt(in_value))) {
|
98
|
+
return Form_.index_of_text(e, in_value);
|
99
|
+
}
|
100
|
+
return in_value; // Todo: 'range' error if index too high
|
101
|
+
}
|
102
|
+
|
103
|
+
Form_.set_checkbox = function(e, in_value){
|
104
|
+
var check = /^(true|check|on)$/i;
|
105
|
+
var uncheck = /^(false|uncheck|off)$/i;
|
106
|
+
|
107
|
+
if (check.test(in_value)) {e.checked = true; return;}
|
108
|
+
if (uncheck.test(in_value)) {e.checked = null; return;}
|
109
|
+
e.click();
|
110
|
+
}
|
111
|
+
|
112
|
+
Form_.index_of_text = function(e, text_value) {
|
113
|
+
var i;
|
114
|
+
switch(text_value) {
|
115
|
+
case 'random':
|
116
|
+
i = Math.round(Math.random() * Form_.index_of_last(e));
|
117
|
+
break;
|
118
|
+
case 'last':
|
119
|
+
i = Form_.index_of_last(e);
|
120
|
+
break;
|
121
|
+
default: // search for visible text
|
122
|
+
i = Form_.index_of_match(e, text_value);
|
123
|
+
}
|
124
|
+
if (i == null) throw new Error("Form_.index_of_text '" + text_value + "' not found");
|
125
|
+
return i;
|
126
|
+
}
|
127
|
+
|
128
|
+
// last item in list
|
129
|
+
Form_.index_of_last = function(e) { return e.options.length - 1; }
|
130
|
+
|
131
|
+
Form_.index_of_match = function(e, text_value) {
|
132
|
+
for ( var i = 0; i < e.options.length; i++) {
|
133
|
+
if (Form_.match_to_beginning(e.options[i].text, text_value)) return i;
|
134
|
+
if (Form_.match_to_beginning(e.options[i].value, text_value)) return i;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
Form_.match_to_beginning = function(within_string, sought_text_value) {
|
139
|
+
return (within_string.search(new RegExp('^' + sought_text_value.custom_escape_special(),'i')) > -1);
|
140
|
+
}
|
141
|
+
|
142
|
+
Form_.prototype.droplist_size = function(element_name) {return Form_.index_of_last( this.form.elements[element_name] )}
|
143
|
+
|
144
|
+
Form_.prototype.visible_elements = function() {
|
145
|
+
var element_list = [];
|
146
|
+
var e = this.form.elements;
|
147
|
+
for(var i = 0; i < e.length; i++){
|
148
|
+
if (e[i].type != 'hidden') {
|
149
|
+
if (e[i].name)
|
150
|
+
element_list.push(e[i].name);
|
151
|
+
else
|
152
|
+
element_list.push(e[i].value);
|
153
|
+
}
|
154
|
+
}
|
155
|
+
return element_list;
|
156
|
+
}
|
157
|
+
|
158
|
+
Form_.prototype.enabled_elements = function() {
|
159
|
+
var element_list = [];
|
160
|
+
var e = this.form.elements;
|
161
|
+
for(var i = 0; i < e.length; i++){
|
162
|
+
if (!e[i].disabled && e[i].type != 'hidden') {
|
163
|
+
if (e[i].name)
|
164
|
+
element_list.push(e[i].name);
|
165
|
+
else
|
166
|
+
element_list.push(e[i].value);
|
167
|
+
}
|
168
|
+
}
|
169
|
+
return element_list;
|
170
|
+
}
|
171
|
+
|
172
|
+
Form_.prototype.element_indexes = function() {
|
173
|
+
var element_list = [];
|
174
|
+
var e = this.form.elements;
|
175
|
+
for(var i = 0; i < e.length; i++){
|
176
|
+
element_list.push(e[i].name + '|' + i);
|
177
|
+
}
|
178
|
+
return element_list;
|
179
|
+
}
|
180
|
+
|
181
|
+
// returns double delimited list of name-value pairs, but only for elements having values
|
182
|
+
Form_.prototype.values = function() {
|
183
|
+
var opt_index, element_name, element_text;
|
184
|
+
var values = '';
|
185
|
+
var target_types = /(text|select|radio|checkbox|password|file)/i;
|
186
|
+
var checkable = /(radio|checkbox)/i;
|
187
|
+
|
188
|
+
var e = this.form.elements;
|
189
|
+
|
190
|
+
for(var i = 0;i < e.length; i++){
|
191
|
+
if (target_types.test(e[i].type)) {
|
192
|
+
element_name = e[i].name;
|
193
|
+
element_text = e[i].value.custom_trim();
|
194
|
+
if ('selectedIndex' in e[i] && e[i].selectedIndex > -1) {
|
195
|
+
if (e[i].type == 'select-multiple') {
|
196
|
+
values += Form_.multiple_selections(element_name, e[i].options);
|
197
|
+
continue;
|
198
|
+
}
|
199
|
+
opt_index = e[i].selectedIndex;
|
200
|
+
element_text = e[i].options[opt_index].text.custom_trim();
|
201
|
+
}
|
202
|
+
if (checkable.test(e[i].type) && 'checked' in e[i]) {
|
203
|
+
if (!e[i].checked) element_text = '';
|
204
|
+
}
|
205
|
+
if (element_text) values += element_name + '=>' + element_text + Form_.FIELD_DELIMITER;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
return values;
|
209
|
+
}
|
210
|
+
|
211
|
+
Form_.multiple_selections = function(element_name, option_collection) {
|
212
|
+
var values = '';
|
213
|
+
for(var i = 0; i < option_collection.length; i++) {
|
214
|
+
if (option_collection[i].selected)
|
215
|
+
values += element_name + '=>' + option_collection[i].text.custom_trim() + Form_.FIELD_DELIMITER;
|
216
|
+
}
|
217
|
+
return values;
|
218
|
+
}
|
219
|
+
|
220
|
+
Form_.prototype.all_checked = function() {
|
221
|
+
var element_list = [];
|
222
|
+
var e = this.form.elements;
|
223
|
+
for(var i = 0; i < e.length; i++) {
|
224
|
+
if (e[i].type == 'checkbox' && e[i].checked) element_list.push(e[i].name);
|
225
|
+
}
|
226
|
+
return element_list;
|
227
|
+
}
|
228
|
+
|
229
|
+
Form_.prototype.type_count = function(element_type) {
|
230
|
+
var e = this.form.elements;
|
231
|
+
var count = 0;
|
232
|
+
for(var i = 0; i < e.length; i++) {
|
233
|
+
if (e[i].type.toLowerCase() == element_type) count++;
|
234
|
+
}
|
235
|
+
return count;
|
236
|
+
}
|
237
|
+
|
238
|
+
Form_.prototype.all_button_names = function() {
|
239
|
+
var a = [];
|
240
|
+
var buttons = this.buttons();
|
241
|
+
for(var i = 0; i < buttons.length; i++){
|
242
|
+
if (buttons[i].name)
|
243
|
+
a.push(buttons[i].name);
|
244
|
+
else
|
245
|
+
a.push(buttons[i].value);
|
246
|
+
}
|
247
|
+
return a;
|
248
|
+
}
|
249
|
+
|
250
|
+
// collection of button objects, NOT names
|
251
|
+
Form_.prototype.buttons = function() {
|
252
|
+
var a = [];
|
253
|
+
var button_types = /(button|submit|reset)/i;
|
254
|
+
var e = this.form.elements;
|
255
|
+
for(var i = 0; i < e.length; i++){ if (button_types.test(e[i].type)) a.push(e[i]); }
|
256
|
+
return a;
|
257
|
+
}
|
258
|
+
|
259
|
+
Form_.prototype.first_checkbox = function(name_pattern) {
|
260
|
+
var e = this.form.elements;
|
261
|
+
for(var i = 0;i < e.length; i++)
|
262
|
+
if (e[i].type.toLowerCase() == 'checkbox' && name_pattern.test(e[i].name))
|
263
|
+
return e[i].name;
|
264
|
+
}
|
265
|
+
|
266
|
+
Form_.prototype.checkbox_names = function() {
|
267
|
+
var a = [];
|
268
|
+
var e = this.form.elements;
|
269
|
+
for(var i = 0;i < e.length; i++){
|
270
|
+
if (e[i].type.toLowerCase() == 'checkbox') a.push(e[i].name);
|
271
|
+
}
|
272
|
+
return a;
|
273
|
+
}
|
274
|
+
|
275
|
+
Form_.prototype.clear_checkboxes = function() {
|
276
|
+
var e = this.form.elements;
|
277
|
+
for(var i = 0;i < e.length; i++)
|
278
|
+
if (e[i].type.toLowerCase() == 'checkbox') e[i].checked = null;
|
279
|
+
}
|
280
|
+
|
281
|
+
Form_.prototype.set_checkboxes = function() {
|
282
|
+
var e = this.form.elements;
|
283
|
+
for(var i = 0;i < e.length; i++)
|
284
|
+
if (e[i].type.toLowerCase() == 'checkbox') e[i].checked = true;
|
285
|
+
}
|
286
|
+
|
287
|
+
/* Table_ Class */
|
288
|
+
// Table_ class constructor: provides reference to aut document and a specified table
|
289
|
+
// table can be identified by name or index
|
290
|
+
// if using name, caller responsible for wrapping in single quotes: e.g. Table_(this, "'tblDocsGenerated'")
|
291
|
+
function Table_(page, table_id_or_index) {
|
292
|
+
this.table = aut_document(page).getElementsByTagName('table')[table_id_or_index];
|
293
|
+
}
|
294
|
+
|
295
|
+
// 'TD's or cells for all rows
|
296
|
+
Table_.prototype.cells = function() { return this.table.getElementsByTagName('td') }
|
297
|
+
|
298
|
+
Table_.prototype.size = function(){ return this.table.rows.length;}
|
299
|
+
|
300
|
+
|
301
|
+
Table_.text_from_cell_collection = function(cells){
|
302
|
+
var cell_text, values = '';
|
303
|
+
for(var i = 0; i < cells.length; i++){
|
304
|
+
cell_text = text(cells[i]).custom_trim();
|
305
|
+
if (cell_text) values += cell_text + '|';
|
306
|
+
}
|
307
|
+
return values;
|
308
|
+
}
|
309
|
+
|
310
|
+
Table_.prototype.all_cell_ids_and_text = function(){
|
311
|
+
var values = '';
|
312
|
+
var cell_text;
|
313
|
+
var cells = this.cells();
|
314
|
+
|
315
|
+
for(var i = 0; i < cells.length; i++){
|
316
|
+
if (cells[i].id) {
|
317
|
+
cell_text = text(cells[i]).custom_trim();
|
318
|
+
if (cell_text) values += cells[i].id + '=>' + cell_text + '|';
|
319
|
+
}
|
320
|
+
}
|
321
|
+
return values;
|
322
|
+
}
|
323
|
+
|
324
|
+
Table_.prototype.all_cell_text = function(){
|
325
|
+
return Table_.text_from_cell_collection(this.cells());
|
326
|
+
}
|
327
|
+
|
328
|
+
/* Extensions of built-in String Class */
|
329
|
+
String.prototype.custom_trim = function() { return this.replace(/^\s\s*/,'').replace(/\s\s*$/, '').replace(/^\xa0+$/, ''); }
|
330
|
+
|
331
|
+
String.prototype.custom_escape_special = function() {
|
332
|
+
var special_characters = /([.*+?^${}()|[\]\/\\])/g;
|
333
|
+
return this.replace(special_characters, '\\$1');
|
334
|
+
}
|
335
|
+
|
336
|
+
/* Stand-alone Functions */
|
337
|
+
function all_form_names(page) {
|
338
|
+
var names = [];
|
339
|
+
var forms_collection = aut_document(page).forms;
|
340
|
+
|
341
|
+
for (var f = 0; f < forms_collection.length; f++) {
|
342
|
+
if (forms_collection[f].name)
|
343
|
+
names.push(forms_collection[f].name);
|
344
|
+
else
|
345
|
+
names.push(f);
|
346
|
+
|
347
|
+
}
|
348
|
+
return names;
|
349
|
+
}
|
350
|
+
|
351
|
+
// IE or W3C
|
352
|
+
function text(obj) {
|
353
|
+
var text_value = obj.innerText || obj.textContent;
|
354
|
+
// windows IE: some cases the above '||' returns 'undefined'; explicit assignment without '||' works for some reason
|
355
|
+
if (text_value == undefined) {text_value = obj.innerText;}
|
356
|
+
|
357
|
+
return text_value;
|
358
|
+
}
|
359
|
+
|
data/lib/hash.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Hash
|
2
|
+
# used by HoneyDo methods
|
3
|
+
def keys_to_sym
|
4
|
+
sym_key_hash = Hash.new()
|
5
|
+
self.each { |k, v| sym_key_hash[k.to_sym] = v}
|
6
|
+
return sym_key_hash
|
7
|
+
end
|
8
|
+
|
9
|
+
# if a value is non-scalar, i.e. not a single value but a list of delimited ones, put each into its own pair
|
10
|
+
def split_compound_values(delimiter = "|")
|
11
|
+
new_pairs = Hash.new()
|
12
|
+
self.each do |key, value|
|
13
|
+
if value.include?(delimiter)
|
14
|
+
value_array = value.split(delimiter)
|
15
|
+
value_array.each_index do |i|
|
16
|
+
unless new_pairs.has_key?(key)
|
17
|
+
new_pairs[key] = value_array[i] # first value goes in pair with original key name
|
18
|
+
else
|
19
|
+
new_pairs[(key.to_s + "_" + i.to_s).to_sym] = value_array[i] # add index, starting with 1, to new key names for remaining values
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
#merge in new pairs, which replaces any that had a compound value with a pair having the original key and the first value only
|
25
|
+
return self.merge(new_pairs)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/honey_do.rb
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
# HoneyDo
|
2
|
+
# Selenium#SeleniumDriver
|
3
|
+
require 'hash'
|
4
|
+
require 'symbol'
|
5
|
+
require 'selenium'
|
6
|
+
|
7
|
+
# =Just give it a list!
|
8
|
+
# ==== set_form_values provides a simple, consistent interface to any collection of HTML form elements.
|
9
|
+
# * Set one or many values and click one or many controls with one call
|
10
|
+
# * No element type or "action" command is ever needed
|
11
|
+
# * List can be a Ruby string, array, hash, or symbol
|
12
|
+
# * Freely mix name/value pairs with just names, at your convenience
|
13
|
+
# * Event handlers are fired normally, and Ajax calls just seem to work
|
14
|
+
#
|
15
|
+
# ==== get_form_values and other methods provide simple, consistent means of reading all form data at once
|
16
|
+
#
|
17
|
+
# ==== user-extensions.js[link:\files\js\user-extensions_js.html]
|
18
|
+
# * class <tt>Form_</tt> enables passing collections to and from HTML forms.
|
19
|
+
# * class <tt>Table_</tt> provides a couple of HTML table readers, which return collections of cell values. Non-form HTML is not at all generic so this isn't fleshed out for the release version. It's included mostly for demo purposes.
|
20
|
+
module HoneyDo
|
21
|
+
|
22
|
+
# ==_input_list_
|
23
|
+
# * <b>meant to be flexible and tolerant</b> so you can code your data in whatever way is handy to the situation
|
24
|
+
# * any mix of HTML form element <tt>name</tt>s and pairs of element <tt>name</tt>s and values.
|
25
|
+
# * can be a single string, single symbol, single integer, hash map, or array consisting of any number of strings, symbols, or 2 element arrays
|
26
|
+
#
|
27
|
+
# ====HTML +form+ element identifiers
|
28
|
+
# <b><tt>name</tt> attribute</b>:: YES
|
29
|
+
# <b><tt>form.elements</tt> index</b>:: YES
|
30
|
+
# <b><tt>id</tt> attribute</b>:: NO
|
31
|
+
# <b><tt>value</tt> attribute</b>:: NO
|
32
|
+
#
|
33
|
+
# <b>element names (or indexes) without values get <i>clicked </i></b> if they are
|
34
|
+
# clickable:: <tt>button, submit, reset, radio, checkbox</tt>
|
35
|
+
# sel.set_form_values(:clearButton)
|
36
|
+
#
|
37
|
+
# sel.set_form_values([:includeDocuments_cb, :requireSig_cb])
|
38
|
+
#
|
39
|
+
# sel.set_form_values("resetButton")
|
40
|
+
#
|
41
|
+
# sel.set_form_values(0)
|
42
|
+
#
|
43
|
+
# <b>element names (or indexes) with values get <i>set</i></b> if they are
|
44
|
+
# 1. settable: <tt>text, textarea, password</tt>
|
45
|
+
# 1. selectable: <tt>select-one, select-mulitple</tt>
|
46
|
+
# 1. a grouped item with a value: <tt>checkbox, radio</tt>
|
47
|
+
# sel.set_form_values(:city => "Appleton")
|
48
|
+
#
|
49
|
+
# sel.set_form_values(:dealerid => "Foster-Stevens", :includeDocuments_cb => :check)
|
50
|
+
#
|
51
|
+
# sel.set_form_values(:state => "IL", :street_1 => "113 Foo St.", :city => "Sandwich", :zip => 60548, :same_as_shipping => true)
|
52
|
+
#
|
53
|
+
# sel.set_form_values(:color => 8,
|
54
|
+
# :hobbies => "hacking",
|
55
|
+
# :username => "ernie",
|
56
|
+
# :password => "bert",
|
57
|
+
# :further_description => "Test Input String blah " * 50,
|
58
|
+
# :extras => 1,
|
59
|
+
# :browser => "safari")
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# <b>values for _clickable_ elements are ignored</b>. They are useless but harmless.
|
63
|
+
# sel.set_form_values(:clearButton => "Clear")
|
64
|
+
#
|
65
|
+
# sel.set_form_values(3 => "Clear")
|
66
|
+
# <tt>"Clear"</tt> is the element's +value+ attribute (or visible text) and is ignored in both cases.
|
67
|
+
# <tt>:clearButton</tt> is the +name+ attribute and is used;
|
68
|
+
# <tt>3</tt> is the element's index (within <tt>form.elements</tt>) and would also be used.
|
69
|
+
#
|
70
|
+
# <b>input as array guarantees input order</b> and allows us to mix in _clickables_ without dummy values
|
71
|
+
# sel.set_form_values([[:search_customer_name, "Foster"], [:search_customer_city, "Wentworth"], :submitSearchForm])
|
72
|
+
#
|
73
|
+
# ==_processing_params_
|
74
|
+
# <b>These aren't usually necessary</b>.
|
75
|
+
# <tt>:form_name_or_index</tt>:: Defaults to 0, because there is usually just one form per page. +String+ or +Integer+
|
76
|
+
# <tt>:ok_if_not_found</tt>:: +true+ to swallow the error, +nil+/+false+/+null+ if not present (the default). Useful for testing multiple versions of your app when names have changed, or widgets have been added or removed.
|
77
|
+
# <tt>:one_field_at_a_time</tt>:: +true+ to use mulitple-call mode. Makes Ruby loop on the list, instead JavaScript, forcing a separate <tt>get_eval</tt> call for each item. Effectively slows it down as a means of handling _Ajax_ refreshes in _Firefox_.
|
78
|
+
# <tt>:retry_max</tt>:: 0 or 1 to *_not_* retry at all. Anything > 2 (the default, which means 1 try and 1 retry) is probably useless, but I don't know your app. +Integer+
|
79
|
+
# <tt>:retry_interval</tt>:: To change the default wait time (0.75 seconds) between retries. +Float+
|
80
|
+
#
|
81
|
+
# <b>Make sure your +input_list+ items and +processing_params+ are in separate data structures in the arg list</b>
|
82
|
+
# sel.set_form_values(:search_customer_name => "Foster", :form_name_or_index => "form_2") # WRONG! that's a 2-item input list
|
83
|
+
#
|
84
|
+
# sel.set_form_values({:search_customer_name => "Foster"}, :form_name_or_index => "form_2") # RIGHT! Ruby will make the 2nd hash for you
|
85
|
+
def set_form_values(input_list, processing_params = {})
|
86
|
+
which_form = processing_params[:form_name_or_index] ? processing_params[:form_name_or_index] : 0
|
87
|
+
retry_max = processing_params[:retry_max] ? processing_params[:retry_max] : 2
|
88
|
+
retry_interval = processing_params[:retry_interval] ? processing_params[:retry_interval] : 0.75
|
89
|
+
|
90
|
+
js_open = "dom=new Form_( this, '#{which_form}' )."
|
91
|
+
js_close = ", '#{processing_params[:ok_if_not_found]}' )"
|
92
|
+
errors = ""
|
93
|
+
|
94
|
+
# when input is a single symbol or int
|
95
|
+
input_list = input_list.to_s if [Symbol, Fixnum].include?(input_list.class)
|
96
|
+
|
97
|
+
re_try(retry_max, retry_interval) do
|
98
|
+
if processing_params[:one_field_at_a_time]
|
99
|
+
# we can be a hash, array, or string here: 'each' with a single |arg| treats us as an array.
|
100
|
+
input_list.each { |pair| errors << get_eval(js_open + "set_value( #{pair_to_args(pair)}" + js_close) }
|
101
|
+
else
|
102
|
+
errors = get_eval(js_open + "set_values( '#{list_to_big_string(input_list)}'" + js_close)
|
103
|
+
end
|
104
|
+
raise_js_errors(errors)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# hash map of element names to values, but only for those elements that have values.
|
109
|
+
def get_form_values(form_name_or_index=0)
|
110
|
+
js = "dom=new Form_(this, '#{form_name_or_index}').values()"
|
111
|
+
to_hash(get_eval(js)).keys_to_sym
|
112
|
+
end
|
113
|
+
|
114
|
+
# gets comma separated list of non-hidden form element names
|
115
|
+
def get_form_elements(form_name_or_index=0)
|
116
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').visible_elements()")
|
117
|
+
end
|
118
|
+
|
119
|
+
# table (nested array) of element names and indices. 0 based because the DOM is.
|
120
|
+
def get_form_element_indexes(form_name_or_index=0)
|
121
|
+
element_index_table = []
|
122
|
+
element_indexes = get_eval("dom=new Form_(this, '#{form_name_or_index}').element_indexes()").split(',')
|
123
|
+
element_indexes.each {|item| element_index_table << item.split(FIELD_DELIMITER)}
|
124
|
+
return element_index_table
|
125
|
+
end
|
126
|
+
|
127
|
+
# gets comma separated list of non-hidden, non-disabled form element names
|
128
|
+
def get_enabled_form_elements(form_name_or_index=0)
|
129
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').enabled_elements()")
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_all_button_names(form_name_or_index=0)
|
133
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').all_button_names()")
|
134
|
+
end
|
135
|
+
|
136
|
+
# given a javascript friendly regex, returns the first matching name
|
137
|
+
def get_checkbox_name_by_pattern(form_name, name_pattern)
|
138
|
+
get_eval("dom=new Form_(this, '#{form_name}').first_checkbox(#{name_pattern})").to_sym
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_all_checkbox_names(form_name_or_index = 0)
|
142
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').checkbox_names()")
|
143
|
+
end
|
144
|
+
|
145
|
+
def get_droplist_size(element_name, form_name_or_index = 0)
|
146
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').droplist_size('#{element_name}')").to_i
|
147
|
+
end
|
148
|
+
|
149
|
+
def get_all_checked(form_name_or_index = 0)
|
150
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').all_checked()").split(',')
|
151
|
+
end
|
152
|
+
|
153
|
+
def clear_checkboxes(form_name_or_index = 0)
|
154
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').clear_checkboxes()")
|
155
|
+
end
|
156
|
+
|
157
|
+
def set_all_checkboxes(form_name_or_index = 0)
|
158
|
+
clear_checkboxes
|
159
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').set_checkboxes()")
|
160
|
+
end
|
161
|
+
|
162
|
+
# HTML element count of checkboxes
|
163
|
+
def checkbox_count(form_name_or_index = 0)
|
164
|
+
get_eval("dom=new Form_(this, '#{form_name_or_index}').type_count('checkbox')").to_i
|
165
|
+
end
|
166
|
+
|
167
|
+
# comma delimited list of form names. could include index value if no name attribute on the form
|
168
|
+
def get_all_form_names
|
169
|
+
get_eval("dom=all_form_names(this)")
|
170
|
+
end
|
171
|
+
|
172
|
+
# table reader - hash of <td> id's and text for cells that have text
|
173
|
+
def get_cell_ids_and_text(table_id_or_index=0)
|
174
|
+
js = "dom=new Table_(this, '#{table_id_or_index}').all_cell_ids_and_text()"
|
175
|
+
to_hash(get_eval(js)).keys_to_sym
|
176
|
+
end
|
177
|
+
|
178
|
+
# table reader - returns comma delimited string of all <td> text
|
179
|
+
def get_all_cell_text(table_id_or_index)
|
180
|
+
js = "dom=new Table_(this, '#{table_id_or_index}').all_cell_text()"
|
181
|
+
get_eval(js)
|
182
|
+
end
|
183
|
+
|
184
|
+
# HTML table row count
|
185
|
+
def get_table_size(table_id_or_index)
|
186
|
+
get_eval("dom=new Table_(this, '#{table_id_or_index}').size()").to_i
|
187
|
+
end
|
188
|
+
|
189
|
+
protected
|
190
|
+
|
191
|
+
FIELD_DELIMITER = "|"
|
192
|
+
PAIR_DELIMITER = ":::"
|
193
|
+
|
194
|
+
# convert string from Form_.values
|
195
|
+
def to_hash(fields_string, delimiter = FIELD_DELIMITER)
|
196
|
+
fields_hash = {}
|
197
|
+
fields_string.split(delimiter).each do |field_name|
|
198
|
+
key_field_name, value_text = field_name.split('=>')
|
199
|
+
|
200
|
+
if fields_hash[key_field_name] # group items (checkboxes, radio buttons); duplicate 'id's
|
201
|
+
fields_hash[key_field_name] += delimiter + value_text
|
202
|
+
else
|
203
|
+
fields_hash[key_field_name] = value_text
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
return fields_hash
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
# try again if input chokes
|
213
|
+
def re_try(max_tries, interval) # :doc:
|
214
|
+
tries = 0
|
215
|
+
begin
|
216
|
+
tries += 1
|
217
|
+
yield
|
218
|
+
rescue SeleniumCommandError, RuntimeError => ex
|
219
|
+
sleep interval
|
220
|
+
retry if tries < max_tries
|
221
|
+
raise "#{ex.message}: tries = #{tries}"
|
222
|
+
rescue Exception => ex
|
223
|
+
raise "Exception: #{ex.message}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# prepares input list for Form_.prototype.set_values(), which sets all fields with one call
|
228
|
+
def list_to_big_string(input_list)
|
229
|
+
array_of_strings = input_list.collect {|i| i.respond_to?(:join) ? i.join(PAIR_DELIMITER) : i }
|
230
|
+
|
231
|
+
return array_of_strings.join(FIELD_DELIMITER) if array_of_strings.respond_to?(:join)
|
232
|
+
return array_of_strings
|
233
|
+
end
|
234
|
+
|
235
|
+
# when Ruby loops on the input list, ensure the name and possible value is in 2 arguments
|
236
|
+
def pair_to_args(input_pair)
|
237
|
+
return "'#{input_pair[0]}', '#{input_pair[1]}'" if input_pair.instance_of?(Array)
|
238
|
+
return "'#{input_pair}', ''"
|
239
|
+
end
|
240
|
+
|
241
|
+
# handle errors from Form_.prototype.set_values() or Form_.prototype.set_value()
|
242
|
+
def raise_js_errors(error_string) # :doc:
|
243
|
+
unless error_string.empty?
|
244
|
+
errors = error_string.split("\n")
|
245
|
+
formatted_errors = errors.collect{|error_text| "\njavascript error:\n" + error_text.gsub(PAIR_DELIMITER, "=")}
|
246
|
+
raise RuntimeError.new(formatted_errors)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
# Mix HoneyDo methods in so they can be called on SeleniumDriver object
|
253
|
+
#
|
254
|
+
# Note that within HoneyDo scope, +get_eval+ calls work because of this mixin
|
255
|
+
class Selenium::SeleniumDriver
|
256
|
+
include HoneyDo
|
257
|
+
end
|