redsquirrel-safariwatir 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +46 -0
- data/Rakefile +13 -0
- data/lib/safariwatir/core_ext.rb +31 -0
- data/lib/safariwatir/scripter.rb +585 -0
- data/lib/safariwatir.rb +547 -0
- data/lib/watir/exceptions.rb +42 -0
- data/safariwatir.gemspec +34 -0
- data/safariwatir_example.rb +151 -0
- metadata +79 -0
data/README.rdoc
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
= SafariWatir
|
2
|
+
|
3
|
+
* http://safariwatir.rubyforge.org
|
4
|
+
* http://rubyforge.org/mailman/listinfo/safariwatir-general
|
5
|
+
* http://twitter.com/SafariWatir
|
6
|
+
|
7
|
+
== DESCRIPTION:
|
8
|
+
|
9
|
+
We are putting Watir on Safari.
|
10
|
+
The original Watir (Web Application Testing in Ruby) project supports only IE on Windows.
|
11
|
+
This project aims at adding Watir support for Safari on the Mac.
|
12
|
+
|
13
|
+
== Requirements
|
14
|
+
|
15
|
+
Mac OS X running Safari. Some features require you to turn on "Enable access for assistive devices" in System Preferences > Universal Access.
|
16
|
+
|
17
|
+
|
18
|
+
== SYNOPSIS:
|
19
|
+
|
20
|
+
require 'rubygems'
|
21
|
+
require 'safariwatir'
|
22
|
+
|
23
|
+
browser = Watir::Safari.new
|
24
|
+
browser.goto("http://google.com")
|
25
|
+
browser.text_field(:name, "q").set("obtiva")
|
26
|
+
browser.button(:name, "btnI").click
|
27
|
+
puts "FAILURE" unless browser.contains_text("software")
|
28
|
+
|
29
|
+
== INSTALL:
|
30
|
+
|
31
|
+
[sudo] gem install safariwatir
|
32
|
+
|
33
|
+
or
|
34
|
+
|
35
|
+
git clone git://github.com/redsquirrel/safariwatir.git
|
36
|
+
cd safariwatir
|
37
|
+
rake install
|
38
|
+
|
39
|
+
== RUNNING SAFARIWATIR AGAINST WATIR'S CORE TESTS
|
40
|
+
|
41
|
+
# First, install the SafariWatir gem (see above)
|
42
|
+
svn checkout https://svn.openqa.org/svn/watir/trunk
|
43
|
+
cd trunk/watir
|
44
|
+
cp unittests/options.yml.example unittests/options.yml
|
45
|
+
# Edit unittests/options.yml and set browser: safari
|
46
|
+
ruby unittests/core_tests.rb
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('safariwatir', '0.3.5') do | config |
|
6
|
+
config.summary = %q{Automated testing tool for web applications.}
|
7
|
+
config.description = %q{WATIR stands for "Web Application Testing in Ruby". See WATIR project for more information. This is a Safari-version of the original IE-only WATIR.}
|
8
|
+
config.url = 'http://safariwatir.rubyforge.org/'
|
9
|
+
config.author = 'Dave Hoover'
|
10
|
+
config.email = 'dave@obtiva.com'
|
11
|
+
config.ignore_pattern = ['tmp/*', 'script/*']
|
12
|
+
config.development_dependencies = ["rb-appscript"]
|
13
|
+
end
|
@@ -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,585 @@
|
|
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 operate_by_table_cell(element = @element)
|
150
|
+
%|var element = document;
|
151
|
+
if (element == undefined) {
|
152
|
+
return '#{TABLE_CELL_NOT_FOUND}';
|
153
|
+
}
|
154
|
+
#{yield}|
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_value_for(element = @element)
|
158
|
+
execute(element.operate { %|return element.value;| }, element)
|
159
|
+
end
|
160
|
+
|
161
|
+
def document_text
|
162
|
+
execute(%|return document.getElementsByTagName('BODY').item(0).innerText;|)
|
163
|
+
end
|
164
|
+
|
165
|
+
def document_html
|
166
|
+
execute(%|return document.all[0].outerHTML;|)
|
167
|
+
end
|
168
|
+
|
169
|
+
def document_title
|
170
|
+
execute(%|return document.title;|)
|
171
|
+
end
|
172
|
+
|
173
|
+
def focus(element)
|
174
|
+
execute(element.operate { %|element.focus();| }, element)
|
175
|
+
end
|
176
|
+
|
177
|
+
def blur(element)
|
178
|
+
execute(element.operate { %|element.blur();| }, element)
|
179
|
+
end
|
180
|
+
|
181
|
+
def highlight(element, &block)
|
182
|
+
execute(element.operate do
|
183
|
+
%|element.originalColor = element.style.backgroundColor;
|
184
|
+
element.style.backgroundColor = 'yellow';|
|
185
|
+
end, element)
|
186
|
+
|
187
|
+
@element = element
|
188
|
+
instance_eval(&block)
|
189
|
+
@element = nil
|
190
|
+
|
191
|
+
execute_and_ignore(element.operate { %|element.style.backgroundColor = element.originalColor;| })
|
192
|
+
end
|
193
|
+
|
194
|
+
def element_exists?(element = @element, &block)
|
195
|
+
block ||= Proc.new {}
|
196
|
+
execute(element.operate(&block), element)
|
197
|
+
return true
|
198
|
+
rescue UnknownObjectException
|
199
|
+
return false
|
200
|
+
end
|
201
|
+
|
202
|
+
def select_option(element = @element)
|
203
|
+
execute(element.operate do
|
204
|
+
%|var selected = -1;
|
205
|
+
var previous_selection = 0;
|
206
|
+
for (var i = 0; i < element.options.length; i++) {
|
207
|
+
if (element.options[i].selected) {
|
208
|
+
previous_selection = i;
|
209
|
+
}
|
210
|
+
if (element.options[i].#{element.how} == '#{element.what}') {
|
211
|
+
element.options[i].selected = true;
|
212
|
+
selected = i;
|
213
|
+
}
|
214
|
+
}
|
215
|
+
if (selected == -1) {
|
216
|
+
return '#{ELEMENT_NOT_FOUND}';
|
217
|
+
} else if (previous_selection != selected) {
|
218
|
+
element.selectedIndex = selected;
|
219
|
+
dispatchOnChange(element.options[selected]);
|
220
|
+
}
|
221
|
+
|
|
222
|
+
end, element)
|
223
|
+
end
|
224
|
+
|
225
|
+
def option_exists?(element = @element)
|
226
|
+
element_exists?(element) { handle_option(element) }
|
227
|
+
end
|
228
|
+
|
229
|
+
def handle_option(select_list)
|
230
|
+
%|var option_found = false;
|
231
|
+
for (var i = 0; i < element.options.length; i++) {
|
232
|
+
if (element.options[i].#{select_list.how} == '#{select_list.what}') {
|
233
|
+
option_found = true;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
if (!option_found) {
|
237
|
+
return '#{ELEMENT_NOT_FOUND}';
|
238
|
+
}|
|
239
|
+
end
|
240
|
+
private :handle_option
|
241
|
+
|
242
|
+
def option_selected?(element = @element)
|
243
|
+
element_exists?(element) { handle_selected(element) == "true" }
|
244
|
+
end
|
245
|
+
|
246
|
+
def handle_selected(select_list)
|
247
|
+
%|var selected = false;
|
248
|
+
for (var i = 0; i < element.options.length; i++) {
|
249
|
+
if (element.options[i].#{select_list.how} == '#{select_list.what}' && element.options[i].selected) {
|
250
|
+
selected = true;
|
251
|
+
}
|
252
|
+
}
|
253
|
+
return selected;|
|
254
|
+
end
|
255
|
+
private :handle_option
|
256
|
+
|
257
|
+
def clear_text_input(element = @element)
|
258
|
+
execute(element.operate { %|element.value = '';| }, element)
|
259
|
+
end
|
260
|
+
|
261
|
+
def append_text_input(value, element = @element)
|
262
|
+
sleep typing_lag
|
263
|
+
execute(element.operate do
|
264
|
+
%|element.value += '#{value}';
|
265
|
+
dispatchOnChange(element);
|
266
|
+
element.setSelectionRange(element.value.length, element.value.length);|
|
267
|
+
end, element)
|
268
|
+
end
|
269
|
+
|
270
|
+
def click_element(element = @element)
|
271
|
+
page_load do
|
272
|
+
# 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.
|
273
|
+
execute(element.operate { %|
|
274
|
+
if (element.click) {
|
275
|
+
element.click();
|
276
|
+
} else {
|
277
|
+
if (element.onclick) {
|
278
|
+
var event = DOCUMENT.createEvent('HTMLEvents');
|
279
|
+
event.initEvent('click', true, true);
|
280
|
+
element.onclick(event);
|
281
|
+
} else {
|
282
|
+
var event = DOCUMENT.createEvent('MouseEvents');
|
283
|
+
event.initEvent('click', true, true);
|
284
|
+
element.dispatchEvent(event);
|
285
|
+
}
|
286
|
+
|
287
|
+
}| })
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def click_link(element = @element)
|
292
|
+
click = %/
|
293
|
+
function baseTarget() {
|
294
|
+
var bases = document.getElementsByTagName('BASE');
|
295
|
+
if (bases.length > 0) {
|
296
|
+
return bases[0].target;
|
297
|
+
} else {
|
298
|
+
return;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
function undefinedTarget(target) {
|
302
|
+
return target == undefined || target == '';
|
303
|
+
}
|
304
|
+
function topTarget(target) {
|
305
|
+
return undefinedTarget(target) || target == '_top';
|
306
|
+
}
|
307
|
+
function nextLocation(element) {
|
308
|
+
var target = element.target;
|
309
|
+
if (undefinedTarget(target) && baseTarget()) {
|
310
|
+
top[baseTarget()].location = element.href;
|
311
|
+
} else if (topTarget(target)) {
|
312
|
+
top.location = element.href;
|
313
|
+
} else {
|
314
|
+
top[target].location = element.href;
|
315
|
+
}
|
316
|
+
}
|
317
|
+
var click = DOCUMENT.createEvent('HTMLEvents');
|
318
|
+
click.initEvent('click', true, true);
|
319
|
+
if (element.onclick) {
|
320
|
+
if (false != element.onclick(click)) {
|
321
|
+
nextLocation(element);
|
322
|
+
}
|
323
|
+
} else {
|
324
|
+
nextLocation(element);
|
325
|
+
}/
|
326
|
+
page_load do
|
327
|
+
execute(js.operate(find_link(element), click))
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def operate_on_link(element)
|
332
|
+
js.operate(find_link(element), yield)
|
333
|
+
end
|
334
|
+
|
335
|
+
def find_link(element)
|
336
|
+
case element.how
|
337
|
+
when :index:
|
338
|
+
%|var element = document.getElementsByTagName('A')[#{element.what-1}];|
|
339
|
+
else
|
340
|
+
%|var element = undefined;
|
341
|
+
for (var i = 0; i < document.links.length; i++) {
|
342
|
+
if (document.links[i].#{handle_match(element)}) {
|
343
|
+
element = document.links[i];
|
344
|
+
break;
|
345
|
+
}
|
346
|
+
}|
|
347
|
+
end
|
348
|
+
end
|
349
|
+
private :find_link
|
350
|
+
|
351
|
+
def handle_match(element, how = nil)
|
352
|
+
how = {:text => "text", :url => "href"}[element.how] unless how
|
353
|
+
case element.what
|
354
|
+
when Regexp:
|
355
|
+
%|#{how}.match(/#{element.what.source}/#{element.what.casefold? ? "i":nil})|
|
356
|
+
when String:
|
357
|
+
%|#{how} == '#{element.what}'|
|
358
|
+
else
|
359
|
+
raise RuntimeError, "Unable to locate #{element.name} with #{element.how}"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
private :handle_match
|
363
|
+
|
364
|
+
# Contributed by Kyle Campos
|
365
|
+
def checkbox_is_checked?(element = @element)
|
366
|
+
execute(element.operate { %|return element.checked;| }, element)
|
367
|
+
end
|
368
|
+
|
369
|
+
def operate_by_input_value(element)
|
370
|
+
js.operate(%|
|
371
|
+
var elements = document.getElementsByTagName('INPUT');
|
372
|
+
var element = undefined;
|
373
|
+
for (var i = 0; i < elements.length; i++) {
|
374
|
+
if (elements[i].value == '#{element.what}') {
|
375
|
+
element = elements[i];
|
376
|
+
break;
|
377
|
+
}
|
378
|
+
}|, yield)
|
379
|
+
end
|
380
|
+
|
381
|
+
def operate_by_name(element)
|
382
|
+
js.operate(%|
|
383
|
+
var elements = document.getElementsByName('#{element.what}');
|
384
|
+
var element = undefined;
|
385
|
+
for (var i = 0; i < elements.length; i++) {
|
386
|
+
if (elements[i].tagName != 'META' && elements[i].tagName == '#{element.tag}') {
|
387
|
+
#{handle_form_element_name_match(element)}
|
388
|
+
}
|
389
|
+
}|, yield)
|
390
|
+
end
|
391
|
+
|
392
|
+
def operate_by_class(element)
|
393
|
+
js.operate(%|
|
394
|
+
var elements = document.getElementsByClassName('#{element.what}');
|
395
|
+
var element = elements[0];|, yield)
|
396
|
+
end
|
397
|
+
|
398
|
+
# Checkboxes/Radios have the same name, different values
|
399
|
+
def handle_form_element_name_match(element)
|
400
|
+
element_capture = %|element = elements[i];break;|
|
401
|
+
if element.by_value
|
402
|
+
%|if (elements[i].value == '#{element.by_value}') {
|
403
|
+
#{element_capture}
|
404
|
+
}|
|
405
|
+
else
|
406
|
+
element_capture
|
407
|
+
end
|
408
|
+
end
|
409
|
+
private :handle_form_element_name_match
|
410
|
+
|
411
|
+
def operate_by_id(element)
|
412
|
+
js.operate("var element = document.getElementById('#{element.what}');", yield)
|
413
|
+
end
|
414
|
+
|
415
|
+
def operate_by_index(element)
|
416
|
+
js.operate(%|var element = document.getElementsByTagName('#{element.tag}')[#{element.what-1}];|, yield)
|
417
|
+
end
|
418
|
+
|
419
|
+
def operate_by_src(element, &block)
|
420
|
+
operate_by(element, 'src', &block)
|
421
|
+
end
|
422
|
+
|
423
|
+
def operate_by_text(element, &block)
|
424
|
+
operate_by(element, 'innerText', &block)
|
425
|
+
end
|
426
|
+
|
427
|
+
def operate_by(element, attribute)
|
428
|
+
js.operate(%|var elements = document.getElementsByTagName('#{element.tag}');
|
429
|
+
var element = undefined;
|
430
|
+
for (var i = 0; i < elements.length; i++) {
|
431
|
+
if (elements[i].#{handle_match(element, attribute)}) {
|
432
|
+
element = elements[i];
|
433
|
+
break;
|
434
|
+
}
|
435
|
+
}|, yield)
|
436
|
+
end
|
437
|
+
|
438
|
+
def submit_form(element)
|
439
|
+
page_load do
|
440
|
+
execute(element.operate { %|element.submit();| })
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def click_alert
|
445
|
+
execute_system_events(%|
|
446
|
+
tell window 1
|
447
|
+
if button named "OK" exists then
|
448
|
+
click button named "OK"
|
449
|
+
end if
|
450
|
+
end tell|)
|
451
|
+
end
|
452
|
+
|
453
|
+
def click_security_warning(label)
|
454
|
+
execute_system_events(%|
|
455
|
+
tell window 1
|
456
|
+
tell sheet 1
|
457
|
+
tell group 2
|
458
|
+
if button named "#{label}" exists then
|
459
|
+
click button named "#{label}"
|
460
|
+
return "#{EXTRA_ACTION_SUCCESS}"
|
461
|
+
end if
|
462
|
+
end tell
|
463
|
+
end tell
|
464
|
+
end tell|, true)
|
465
|
+
end
|
466
|
+
|
467
|
+
def for_table(element)
|
468
|
+
AppleScripter.new(TableJavaScripter.new(element))
|
469
|
+
end
|
470
|
+
|
471
|
+
def for_frame(element)
|
472
|
+
# verify the frame exists
|
473
|
+
execute(
|
474
|
+
%|if (parent.#{element.name} == undefined) {
|
475
|
+
return '#{FRAME_NOT_FOUND}';
|
476
|
+
}|, element)
|
477
|
+
AppleScripter.new(FrameJavaScripter.new(element))
|
478
|
+
end
|
479
|
+
|
480
|
+
def speak_value_of(element = @element)
|
481
|
+
speak(get_value_for(element))
|
482
|
+
end
|
483
|
+
|
484
|
+
def speak_text_of(element = @element)
|
485
|
+
speak(element.text)
|
486
|
+
end
|
487
|
+
|
488
|
+
def speak_options_for(element = @element)
|
489
|
+
values = execute(element.operate do
|
490
|
+
%|var values = '';
|
491
|
+
for (var i = 0; i < element.options.length; i++) {
|
492
|
+
if (element.options[i].selected == true) {
|
493
|
+
values += ' ' + element.options[i].text;
|
494
|
+
}
|
495
|
+
}
|
496
|
+
return values|
|
497
|
+
end, element)
|
498
|
+
speak(values)
|
499
|
+
end
|
500
|
+
|
501
|
+
def speak(string)
|
502
|
+
`osascript <<SCRIPT
|
503
|
+
say "#{string.quote_safe}"
|
504
|
+
SCRIPT`
|
505
|
+
nil
|
506
|
+
end
|
507
|
+
|
508
|
+
|
509
|
+
private
|
510
|
+
|
511
|
+
def execute(script, element = nil)
|
512
|
+
response = eval_js(script)
|
513
|
+
case response
|
514
|
+
when NO_RESPONSE:
|
515
|
+
nil
|
516
|
+
when ELEMENT_NOT_FOUND:
|
517
|
+
raise UnknownObjectException, "Unable to locate #{element.name} element with #{element.how} of #{element.what}"
|
518
|
+
when TABLE_CELL_NOT_FOUND:
|
519
|
+
raise UnknownCellException, "Unable to locate a table cell with #{element.how} of #{element.what}"
|
520
|
+
when FRAME_NOT_FOUND:
|
521
|
+
raise UnknownFrameException, "Unable to locate a frame with name #{element.name}"
|
522
|
+
else
|
523
|
+
response
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
def execute_and_ignore(script)
|
528
|
+
eval_js(script)
|
529
|
+
nil
|
530
|
+
end
|
531
|
+
|
532
|
+
# Must have "Enable access for assistive devices" checked in System Preferences > Universal Access
|
533
|
+
def execute_system_events(script, capture_result = false)
|
534
|
+
result = `osascript <<SCRIPT
|
535
|
+
tell application "System Events" to tell process "Safari"
|
536
|
+
#{script}
|
537
|
+
end tell
|
538
|
+
SCRIPT`
|
539
|
+
|
540
|
+
if capture_result && result
|
541
|
+
return result.chomp
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def page_load(extra_action = nil)
|
546
|
+
yield
|
547
|
+
sleep 1
|
548
|
+
|
549
|
+
tries = 0
|
550
|
+
TIMEOUT.times do |tries|
|
551
|
+
if "complete" == eval_js("return DOCUMENT.readyState") && !@document.URL.get.blank?
|
552
|
+
sleep 0.4
|
553
|
+
handle_client_redirect
|
554
|
+
break
|
555
|
+
elsif extra_action
|
556
|
+
result = extra_action.call
|
557
|
+
break if result == EXTRA_ACTION_SUCCESS
|
558
|
+
else
|
559
|
+
sleep 1
|
560
|
+
end
|
561
|
+
end
|
562
|
+
raise "Unable to load page within #{TIMEOUT} seconds" if tries == TIMEOUT-1
|
563
|
+
end
|
564
|
+
|
565
|
+
def handle_client_redirect
|
566
|
+
no_redirect_flag = "proceed"
|
567
|
+
redirect = eval_js(
|
568
|
+
%|var elements = DOCUMENT.getElementsByTagName('META');
|
569
|
+
for (var i = 0; i < elements.length; i++) {
|
570
|
+
if ("refresh" == elements[i].httpEquiv && elements[i].content != undefined && elements[i].content.indexOf(";") != "-1") {
|
571
|
+
return elements[i].content;
|
572
|
+
}
|
573
|
+
}
|
574
|
+
return "#{no_redirect_flag}"|)
|
575
|
+
if redirect != no_redirect_flag
|
576
|
+
time_til_redirect = redirect.split(";").first.to_i
|
577
|
+
sleep time_til_redirect
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
def eval_js(script)
|
582
|
+
@app.do_JavaScript(js.wrap(script), :in => @document)
|
583
|
+
end
|
584
|
+
end # class AppleScripter
|
585
|
+
end
|