jmonteiro-jmonteiro-safariwatir 0.3.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ # Why shouldn't this be in core Ruby?
2
+ class Class
3
+ def def_init(*attrs)
4
+ constructor = %|def initialize(|
5
+ constructor << attrs.map{|a| a.to_s }.join(",")
6
+ constructor << ")\n"
7
+ attrs.each do |attribute|
8
+ constructor << "@#{attribute} = #{attribute}\n"
9
+ end
10
+ constructor << "end"
11
+ class_eval(constructor)
12
+ end
13
+ end
14
+
15
+ class String
16
+ def quote_safe
17
+ gsub(/"/, '\"')
18
+ end
19
+ end
20
+
21
+ class Object
22
+ def blank?
23
+ if respond_to?(:strip)
24
+ strip.empty?
25
+ elsif respond_to?(:empty?)
26
+ empty?
27
+ else
28
+ !self
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,624 @@
1
+ require File.dirname(__FILE__) + '/core_ext'
2
+ require File.dirname(__FILE__) + '/../watir/exceptions'
3
+ require 'appscript'
4
+
5
+ module Watir # :nodoc:
6
+ ELEMENT_NOT_FOUND = "__safari_watir_element_unfound__"
7
+ FRAME_NOT_FOUND = "__safari_watir_frame_unfound__"
8
+ NO_RESPONSE = "__safari_watir_no_response__"
9
+ TABLE_CELL_NOT_FOUND = "__safari_watir_cell_unfound__"
10
+ EXTRA_ACTION_SUCCESS = "__safari_watir_extra_action__"
11
+
12
+ JS_LIBRARY = %|
13
+ function dispatchOnChange(element) {
14
+ var event = document.createEvent('HTMLEvents');
15
+ event.initEvent('change', true, true);
16
+ element.dispatchEvent(event);
17
+ }|
18
+
19
+ class JavaScripter # :nodoc:
20
+ def operate(locator, operation)
21
+ %|#{locator}
22
+ if (element) {
23
+ #{operation}
24
+ } else {
25
+ return '#{ELEMENT_NOT_FOUND}';
26
+ }|
27
+ end
28
+
29
+ def wrap(script)
30
+ # Needed because createEvent must be called on a document, and the JavaScripter sub-classes
31
+ # do some transformations to lower-case "document" before we get here at runtime.
32
+ script.gsub! "DOCUMENT", "document"
33
+
34
+ # Needed because I would like to blindly use return statements, but Safari 3 enforces
35
+ # the ECMAScript standard that return statements are only valid within functions.
36
+ %|#{JS_LIBRARY}
37
+ (function() {
38
+ #{script}
39
+ })()|
40
+ end
41
+
42
+ def find_cell(cell)
43
+ return %|getElementById('#{cell.what}')| if cell.how == :id
44
+ raise RuntimeError, "Unable to use #{cell.how} to find TableCell" unless cell.row
45
+
46
+ finder =
47
+ case cell.row.how
48
+ when :id:
49
+ %|getElementById('#{cell.row.what}')|
50
+ when :index:
51
+ case cell.row.table.how
52
+ when :id
53
+ %|getElementById('#{cell.row.table.what}').rows[#{cell.row.what-1}]|
54
+ when :index:
55
+ %|getElementsByTagName('TABLE')[#{cell.row.table.what-1}].rows[#{cell.row.what-1}]|
56
+ else
57
+ raise MissingWayOfFindingObjectException, "Table element does not support #{cell.row.table.how}"
58
+ end
59
+ else
60
+ raise MissingWayOfFindingObjectException, "TableRow element does not support #{cell.row.how}"
61
+ end
62
+
63
+ finder + %|.cells[#{cell.what-1}]|
64
+ end
65
+ end
66
+
67
+ class FrameJavaScripter < JavaScripter # :nodoc:
68
+ def initialize(frame)
69
+ @page_container = "parent.#{frame.name}"
70
+ end
71
+
72
+ def wrap(script)
73
+ # add in frame name when referencing parent or document
74
+ script.gsub! "parent", "parent.#{@page_container}"
75
+ script.gsub! "document", "#{@page_container}.document"
76
+ super(script)
77
+ end
78
+ end
79
+
80
+ class TableJavaScripter < JavaScripter # :nodoc:
81
+ def_init :cell
82
+
83
+ def wrap(script)
84
+ script.gsub! "document", "document." + find_cell(@cell)
85
+ super(script)
86
+ end
87
+ end
88
+
89
+ class AppleScripter # :nodoc:
90
+ include Watir::Exception
91
+
92
+ attr_reader :js
93
+ attr_accessor :typing_lag
94
+ private :js
95
+
96
+ TIMEOUT = 10
97
+
98
+ def initialize(scripter = JavaScripter.new, opts={})
99
+ @js = scripter
100
+ @appname = opts[:appname] || "Safari"
101
+ @app = Appscript.app(@appname)
102
+ @document = @app.documents[1]
103
+ end
104
+
105
+ def ensure_window_ready
106
+ @app.activate
107
+ @app.make(:new => :document) if @app.documents.get.size == 0
108
+ @document = @app.documents[1]
109
+ end
110
+
111
+ def url
112
+ @document.URL.get
113
+ end
114
+
115
+ def hide
116
+ # because applescript is stupid and annoying you have
117
+ # to get all the processes from System Events, grab
118
+ # the right one for this instance and then set visible
119
+ # of it to false.
120
+ se = Appscript.app("System Events")
121
+ safari = se.processes.get.select do |app|
122
+ app.name.get == @appname
123
+ end.first
124
+ safari.visible.set(false)
125
+ end
126
+
127
+ def close
128
+ @app.quit
129
+ end
130
+
131
+ def navigate_to(url, &extra_action)
132
+ page_load(extra_action) do
133
+ @document.URL.set(url)
134
+ end
135
+ end
136
+
137
+ def reload
138
+ execute(%|window.location.reload()|)
139
+ end
140
+
141
+ def get_text_for(element = @element)
142
+ execute(element.operate { %|return element.innerText| }, element)
143
+ end
144
+
145
+ def get_html_for(element = @element)
146
+ execute(element.operate { %|return element.innerHTML| }, element)
147
+ end
148
+
149
+ def get_target_for(element = @element)
150
+ execute(element.operate { %|return element.htmlFor| }, element)
151
+ end
152
+
153
+ def operate_by_table_cell(element = @element)
154
+ %|var element = document;
155
+ if (element == undefined) {
156
+ return '#{TABLE_CELL_NOT_FOUND}';
157
+ }
158
+ #{yield}|
159
+ end
160
+
161
+ def get_value_for(element = @element)
162
+ execute(element.operate { %|return element.value;| }, element)
163
+ end
164
+
165
+ def document_text
166
+ execute(%|return document.getElementsByTagName('BODY').item(0).innerText;|)
167
+ end
168
+
169
+ def document_html
170
+ execute(%|return document.all[0].outerHTML;|)
171
+ end
172
+
173
+ def document_title
174
+ execute(%|return document.title;|)
175
+ end
176
+
177
+ def focus(element)
178
+ execute(element.operate { %|element.focus();| }, element)
179
+ end
180
+
181
+ def blur(element)
182
+ execute(element.operate { %|element.blur();| }, element)
183
+ end
184
+
185
+ def highlight(element, &block)
186
+ execute(element.operate do
187
+ %|element.originalColor = element.style.backgroundColor;
188
+ element.style.backgroundColor = 'yellow';|
189
+ end, element)
190
+
191
+ @element = element
192
+ instance_eval(&block)
193
+ @element = nil
194
+
195
+ execute_and_ignore(element.operate { %|element.style.backgroundColor = element.originalColor;| })
196
+ end
197
+
198
+ def element_exists?(element = @element, &block)
199
+ block ||= Proc.new {}
200
+ execute(element.operate(&block), element)
201
+ return true
202
+ rescue UnknownObjectException
203
+ return false
204
+ end
205
+
206
+ def select_option(element = @element)
207
+ execute(element.operate do
208
+ %|var selected = -1;
209
+ var previous_selection = 0;
210
+ for (var i = 0; i < element.options.length; i++) {
211
+ if (element.options[i].selected) {
212
+ previous_selection = i;
213
+ }
214
+ if (element.options[i].#{element.how} == '#{element.what}') {
215
+ element.options[i].selected = true;
216
+ selected = i;
217
+ }
218
+ }
219
+ if (selected == -1) {
220
+ return '#{ELEMENT_NOT_FOUND}';
221
+ } else if (previous_selection != selected) {
222
+ element.selectedIndex = selected;
223
+ dispatchOnChange(element.options[selected]);
224
+ }
225
+ |
226
+ end, element)
227
+ end
228
+
229
+ def option_exists?(element = @element)
230
+ element_exists?(element) { handle_option(element) }
231
+ end
232
+
233
+ def handle_option(select_list)
234
+ %|var option_found = false;
235
+ for (var i = 0; i < element.options.length; i++) {
236
+ if (element.options[i].#{select_list.how} == '#{select_list.what}') {
237
+ option_found = true;
238
+ }
239
+ }
240
+ if (!option_found) {
241
+ return '#{ELEMENT_NOT_FOUND}';
242
+ }|
243
+ end
244
+ private :handle_option
245
+
246
+ def option_selected?(element = @element)
247
+ element_exists?(element) { handle_selected(element) == "true" }
248
+ end
249
+
250
+ def handle_selected(select_list)
251
+ %|var selected = false;
252
+ for (var i = 0; i < element.options.length; i++) {
253
+ if (element.options[i].#{select_list.how} == '#{select_list.what}' && element.options[i].selected) {
254
+ selected = true;
255
+ }
256
+ }
257
+ return selected;|
258
+ end
259
+ private :handle_option
260
+
261
+ def clear_text_input(element = @element)
262
+ execute(element.operate { %|element.value = '';| }, element)
263
+ end
264
+
265
+ def append_text_input(value, element = @element)
266
+ sleep typing_lag
267
+ execute(element.operate do
268
+ %|element.value += '#{value}';
269
+ dispatchOnChange(element);
270
+ element.setSelectionRange(element.value.length, element.value.length);|
271
+ end, element)
272
+ end
273
+
274
+ def mouse_over_element(element = @element)
275
+ execute(element.operate { %|
276
+ element.onmouseover();
277
+ |}, element)
278
+ end
279
+
280
+ def click_element(element = @element)
281
+ page_load do
282
+ # Not sure if these events should be either/or. But if you have an image with an onclick, it fires twice without the else clause.
283
+ execute(element.operate { %|
284
+ if (element.click) {
285
+ element.click();
286
+ } else {
287
+ if (element.onclick) {
288
+ var event = DOCUMENT.createEvent('HTMLEvents');
289
+ event.initEvent('click', true, true);
290
+ element.onclick(event);
291
+ } else {
292
+ var event = DOCUMENT.createEvent('MouseEvents');
293
+ event.initEvent('click', true, true);
294
+ element.dispatchEvent(event);
295
+ }
296
+
297
+ }| })
298
+ end
299
+ end
300
+
301
+ def click_link(element = @element)
302
+ click = %/
303
+ function baseTarget() {
304
+ var bases = document.getElementsByTagName('BASE');
305
+ if (bases.length > 0) {
306
+ return bases[0].target;
307
+ } else {
308
+ return;
309
+ }
310
+ }
311
+ function undefinedTarget(target) {
312
+ return target == undefined || target == '';
313
+ }
314
+ function topTarget(target) {
315
+ return undefinedTarget(target) || target == '_top';
316
+ }
317
+ function nextLocation(element) {
318
+ var target = element.target;
319
+ if (undefinedTarget(target) && baseTarget()) {
320
+ top[baseTarget()].location = element.href;
321
+ } else if (topTarget(target)) {
322
+ top.location = element.href;
323
+ } else {
324
+ top[target].location = element.href;
325
+ }
326
+ }
327
+ var click = DOCUMENT.createEvent('HTMLEvents');
328
+ click.initEvent('click', true, true);
329
+ if (element.onclick) {
330
+ if (false != element.onclick(click)) {
331
+ nextLocation(element);
332
+ }
333
+ } else {
334
+ nextLocation(element);
335
+ }/
336
+ page_load do
337
+ execute(js.operate(find_link(element), click))
338
+ end
339
+ end
340
+
341
+ def operate_on_link(element)
342
+ js.operate(find_link(element), yield)
343
+ end
344
+
345
+ def find_link(element)
346
+ case element.how
347
+ when :index:
348
+ %|var element = document.getElementsByTagName('A')[#{element.what-1}];|
349
+ else
350
+ %|var element = undefined;
351
+ for (var i = 0; i < document.links.length; i++) {
352
+ if (document.links[i].#{handle_match(element)}) {
353
+ element = document.links[i];
354
+ break;
355
+ }
356
+ }|
357
+ end
358
+ end
359
+ private :find_link
360
+
361
+ def handle_match(element, how = nil)
362
+ how = {:text => "text", :url => "href"}[element.how] unless how
363
+ case element.what
364
+ when Regexp:
365
+ %|#{how}.match(/#{element.what.source}/#{element.what.casefold? ? "i":nil})|
366
+ when String:
367
+ %|#{how} == '#{element.what}'|
368
+ else
369
+ raise RuntimeError, "Unable to locate #{element.name} with #{element.how}"
370
+ end
371
+ end
372
+ private :handle_match
373
+
374
+ # Contributed by Kyle Campos
375
+ def checkbox_is_checked?(element = @element)
376
+ execute(element.operate { %|return element.checked;| }, element)
377
+ end
378
+
379
+ def element_disabled?(element = @element)
380
+ execute(element.operate { %|return element.disabled;| }, element)
381
+ end
382
+
383
+ def operate_by_input_value(element)
384
+ js.operate(%|
385
+ var elements = document.getElementsByTagName('INPUT');
386
+ var element = undefined;
387
+ for (var i = 0; i < elements.length; i++) {
388
+ if (elements[i].value == '#{element.what}') {
389
+ element = elements[i];
390
+ break;
391
+ }
392
+ }|, yield)
393
+ end
394
+
395
+ def operate_by_name(element)
396
+ js.operate(%|
397
+ var elements = document.getElementsByName('#{element.what}');
398
+ var element = undefined;
399
+ for (var i = 0; i < elements.length; i++) {
400
+ if (elements[i].tagName != 'META' && elements[i].tagName == '#{element.tag}') {
401
+ #{handle_form_element_name_match(element)}
402
+ }
403
+ }|, yield)
404
+ end
405
+
406
+ def operate_by_class(element)
407
+ js.operate(%|
408
+ var elements = document.getElementsByClassName('#{element.what}');
409
+ var element = elements[0];|, yield)
410
+ end
411
+
412
+ # Checkboxes/Radios have the same name, different values
413
+ def handle_form_element_name_match(element)
414
+ element_capture = %|element = elements[i];break;|
415
+ if element.by_value
416
+ %|if (elements[i].value == '#{element.by_value}') {
417
+ #{element_capture}
418
+ }|
419
+ else
420
+ element_capture
421
+ end
422
+ end
423
+ private :handle_form_element_name_match
424
+
425
+ def operate_by_id(element)
426
+ js.operate("var element = document.getElementById('#{element.what}');", yield)
427
+ end
428
+
429
+ def operate_by_index(element)
430
+ js.operate(%|var element = document.getElementsByTagName('#{element.tag}')[#{element.what-1}];|, yield)
431
+ end
432
+
433
+ def operate_by_src(element, &block)
434
+ operate_by(element, 'src', &block)
435
+ end
436
+
437
+ def operate_by_text(element, &block)
438
+ operate_by(element, 'innerText', &block)
439
+ end
440
+
441
+ # TODO: Get this working, or get rid of it
442
+ # this code was from thrashing around. feel free to delete it all
443
+ # def operate_by_label(element, &block)
444
+ # # 1) find the label whose text == target
445
+ # # where does target get set?
446
+ # # 2) get "for" value from the found label
447
+ # # 3) find element with same id as the "for" value
448
+ # # operate_by(element, 'for', &block)
449
+ # # debugger
450
+ # js.operate(%|
451
+ # // alert('document: '+document.methods);
452
+ # var labels = document.getElementsByTagName('LABEL');
453
+ # alert('Found ' + labels.length + ' labels');
454
+ # var element = undefined;
455
+ # for (var i = 0; i < labels.length; i++) {
456
+ # alert('Looking at label for: '+labels[i].htmlFor);
457
+ # if (lables[i].innerText == '#{element.what}') {
458
+ # matching_label = elements[i];
459
+ # alert("found: "+matching_label);
460
+ # element = document.getElementById(matching_label.attributes['for']);
461
+ # break;
462
+ # }
463
+ # }|, yield)
464
+ # end
465
+
466
+ def operate_by(element, attribute)
467
+ js.operate(%|var elements = document.getElementsByTagName('#{element.tag}');
468
+ var element = undefined;
469
+ for (var i = 0; i < elements.length; i++) {
470
+ if (elements[i].#{handle_match(element, attribute)}) {
471
+ element = elements[i];
472
+ break;
473
+ }
474
+ }|, yield)
475
+ end
476
+
477
+ def submit_form(element)
478
+ page_load do
479
+ execute(element.operate { %|element.submit();| })
480
+ end
481
+ end
482
+
483
+ def click_alert
484
+ execute_system_events(%|
485
+ tell window 1
486
+ if button named "OK" exists then
487
+ click button named "OK"
488
+ end if
489
+ end tell|)
490
+ end
491
+
492
+ def click_security_warning(label)
493
+ execute_system_events(%|
494
+ tell window 1
495
+ tell sheet 1
496
+ tell group 2
497
+ if button named "#{label}" exists then
498
+ click button named "#{label}"
499
+ return "#{EXTRA_ACTION_SUCCESS}"
500
+ end if
501
+ end tell
502
+ end tell
503
+ end tell|, true)
504
+ end
505
+
506
+ def for_table(element)
507
+ AppleScripter.new(TableJavaScripter.new(element))
508
+ end
509
+
510
+ def for_frame(element)
511
+ # verify the frame exists
512
+ execute(
513
+ %|if (parent.#{element.name} == undefined) {
514
+ return '#{FRAME_NOT_FOUND}';
515
+ }|, element)
516
+ AppleScripter.new(FrameJavaScripter.new(element))
517
+ end
518
+
519
+ def speak_value_of(element = @element)
520
+ speak(get_value_for(element))
521
+ end
522
+
523
+ def speak_text_of(element = @element)
524
+ speak(element.text)
525
+ end
526
+
527
+ def speak_options_for(element = @element)
528
+ values = execute(element.operate do
529
+ %|var values = '';
530
+ for (var i = 0; i < element.options.length; i++) {
531
+ if (element.options[i].selected == true) {
532
+ values += ' ' + element.options[i].text;
533
+ }
534
+ }
535
+ return values|
536
+ end, element)
537
+ speak(values)
538
+ end
539
+
540
+ def speak(string)
541
+ `osascript <<SCRIPT
542
+ say "#{string.quote_safe}"
543
+ SCRIPT`
544
+ nil
545
+ end
546
+
547
+
548
+ private
549
+
550
+ def execute(script, element = nil)
551
+ response = eval_js(script)
552
+ case response
553
+ when NO_RESPONSE:
554
+ nil
555
+ when ELEMENT_NOT_FOUND:
556
+ raise UnknownObjectException, "Unable to locate #{element.name} element with #{element.how} of #{element.what}"
557
+ when TABLE_CELL_NOT_FOUND:
558
+ raise UnknownCellException, "Unable to locate a table cell with #{element.how} of #{element.what}"
559
+ when FRAME_NOT_FOUND:
560
+ raise UnknownFrameException, "Unable to locate a frame with name #{element.name}"
561
+ else
562
+ response
563
+ end
564
+ end
565
+
566
+ def execute_and_ignore(script)
567
+ eval_js(script)
568
+ nil
569
+ end
570
+
571
+ # Must have "Enable access for assistive devices" checked in System Preferences > Universal Access
572
+ def execute_system_events(script, capture_result = false)
573
+ result = `osascript <<SCRIPT
574
+ tell application "System Events" to tell process "Safari"
575
+ #{script}
576
+ end tell
577
+ SCRIPT`
578
+
579
+ if capture_result && result
580
+ return result.chomp
581
+ end
582
+ end
583
+
584
+ def page_load(extra_action = nil)
585
+ yield
586
+ sleep 1
587
+
588
+ tries = 0
589
+ TIMEOUT.times do |tries|
590
+ if "complete" == eval_js("return DOCUMENT.readyState") && !@document.URL.get.blank?
591
+ sleep 0.4
592
+ handle_client_redirect
593
+ break
594
+ elsif extra_action
595
+ result = extra_action.call
596
+ break if result == EXTRA_ACTION_SUCCESS
597
+ else
598
+ sleep 1
599
+ end
600
+ end
601
+ raise "Unable to load page within #{TIMEOUT} seconds" if tries == TIMEOUT-1
602
+ end
603
+
604
+ def handle_client_redirect
605
+ no_redirect_flag = "proceed"
606
+ redirect = eval_js(
607
+ %|var elements = DOCUMENT.getElementsByTagName('META');
608
+ for (var i = 0; i < elements.length; i++) {
609
+ if ("refresh" == elements[i].httpEquiv && elements[i].content != undefined && elements[i].content.indexOf(";") != "-1") {
610
+ return elements[i].content;
611
+ }
612
+ }
613
+ return "#{no_redirect_flag}"|)
614
+ if redirect != no_redirect_flag
615
+ time_til_redirect = redirect.split(";").first.to_i
616
+ sleep time_til_redirect
617
+ end
618
+ end
619
+
620
+ def eval_js(script)
621
+ @app.do_JavaScript(js.wrap(script), :in => @document)
622
+ end
623
+ end # class AppleScripter
624
+ end