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.
@@ -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
data/lib/symbol.rb ADDED
@@ -0,0 +1,6 @@
1
+ # to sort arrays or hashes with Symbol items or keys
2
+ class Symbol
3
+ def <=>(other)
4
+ self.to_s <=> other.to_s
5
+ end
6
+ end