zucchini-ios 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +11 -1
- data/CHANGELOG.md +6 -0
- data/lib/zucchini.rb +2 -0
- data/lib/zucchini/compiler.rb +59 -0
- data/lib/zucchini/config.rb +19 -15
- data/lib/zucchini/detector.rb +5 -19
- data/lib/zucchini/device.rb +32 -0
- data/lib/zucchini/feature.rb +7 -22
- data/lib/zucchini/runner.rb +1 -2
- data/lib/zucchini/uia/lib/compat.coffee +9 -0
- data/lib/zucchini/uia/lib/mechanic.js +556 -0
- data/lib/zucchini/uia/lib/uia.coffee +14 -0
- data/lib/zucchini/uia/lib/util.coffee +41 -0
- data/lib/zucchini/uia/screen.coffee +29 -31
- data/lib/zucchini/uia/zucchini.coffee +32 -0
- data/lib/zucchini/version.rb +1 -1
- data/spec/lib/zucchini/compiler_spec.rb +40 -0
- data/spec/lib/zucchini/config_spec.rb +6 -14
- data/spec/lib/zucchini/detector_spec.rb +1 -1
- data/spec/lib/zucchini/feature_spec.rb +1 -25
- data/spec/spec_helper.rb +3 -6
- metadata +12 -4
- data/lib/zucchini/uia/base.coffee +0 -89
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## 0.7.0 / 2013-08-11
|
2
|
+
* Integrate mechanic.js - [@vaskas][], [#29][]
|
3
|
+
* Configure simulator with device and orientation - [@phatmann][], [#27][]
|
4
|
+
|
1
5
|
## 0.6.2 / 2013-08-07
|
2
6
|
* Implement screen specific masks - [@phatmann][], [#24][]
|
3
7
|
* Rearrange source files in a more conventional gem way - [@vaskas][], [#25][]
|
@@ -53,6 +57,8 @@
|
|
53
57
|
[#24]: https://github.com/zucchini-src/zucchini/issues/24
|
54
58
|
[#25]: https://github.com/zucchini-src/zucchini/issues/25
|
55
59
|
[#26]: https://github.com/zucchini-src/zucchini/issues/26
|
60
|
+
[#27]: https://github.com/zucchini-src/zucchini/issues/27
|
61
|
+
[#29]: https://github.com/zucchini-src/zucchini/issues/29
|
56
62
|
|
57
63
|
[@Jaco-Pretorius]: https://github.com/Jaco-Pretorius
|
58
64
|
[@NathanSudell]: https://github.com/NathanSudell
|
data/lib/zucchini.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Zucchini
|
2
|
+
module Compiler
|
3
|
+
|
4
|
+
# Compile the feature Javascript file for UIAutomation
|
5
|
+
#
|
6
|
+
# @param orientation [String] initial device orientation, `portrait` or `landscape`
|
7
|
+
# @return [String] path to a compiled js file
|
8
|
+
def compile_js(orientation)
|
9
|
+
js_path = "#{run_data_path}/feature.js"
|
10
|
+
lib_path = File.expand_path(File.dirname(__FILE__))
|
11
|
+
|
12
|
+
coffee_src_paths = [
|
13
|
+
"#{lib_path}/uia",
|
14
|
+
"#{path}/../support/screens",
|
15
|
+
"#{path}/../support/lib",
|
16
|
+
feature_coffee("#{path}/feature.zucchini", orientation)
|
17
|
+
].select { |p| File.exists? p }.join ' '
|
18
|
+
|
19
|
+
"coffee -o #{run_data_path} -j #{js_path} -c #{coffee_src_paths}".tap do |cmd|
|
20
|
+
raise "Error compiling a feature file: #{cmd}" unless system(cmd)
|
21
|
+
end
|
22
|
+
|
23
|
+
concat("#{lib_path}/uia/lib", js_path)
|
24
|
+
js_path
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Wrap feature text into a call to Zucchini client side runner
|
30
|
+
#
|
31
|
+
# @param file [String] path to a feature file
|
32
|
+
# @param orientation [String] initial device orientation
|
33
|
+
# @return [String] path to the resulting CoffeeScript file
|
34
|
+
def feature_coffee(file, orientation)
|
35
|
+
cs_path = "#{run_data_path}/feature.coffee"
|
36
|
+
|
37
|
+
File.open(cs_path, "w+") do |f|
|
38
|
+
feature_text = File.read(file).gsub(/\#.+[\z\n]?/,"").gsub(/\n/, "\\n")
|
39
|
+
f.write "Zucchini('#{feature_text}', '#{orientation}')"
|
40
|
+
end
|
41
|
+
cs_path
|
42
|
+
end
|
43
|
+
|
44
|
+
def concat(lib_path, js_path)
|
45
|
+
tmp_file = "/tmp/feature.js"
|
46
|
+
|
47
|
+
js_src = Dir.glob("#{lib_path}/*.js").inject([]) do |libs, f|
|
48
|
+
libs << File.read(f)
|
49
|
+
end.join(";\n")
|
50
|
+
|
51
|
+
File.open(tmp_file, 'w') do |f|
|
52
|
+
f.puts(js_src)
|
53
|
+
f.puts(File.read(js_path))
|
54
|
+
end
|
55
|
+
|
56
|
+
File.rename(tmp_file, js_path)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/zucchini/config.rb
CHANGED
@@ -20,10 +20,11 @@ module Zucchini
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.app
|
23
|
-
device_name
|
24
|
-
|
23
|
+
device_name = ENV['ZUCCHINI_DEVICE'] || @@default_device_name
|
24
|
+
device = devices[device_name]
|
25
|
+
app_path = File.absolute_path(device['app'] || @@config['app'] || ENV['ZUCCHINI_APP'])
|
25
26
|
|
26
|
-
if device_name == 'iOS Simulator' && !File.exists?(app_path)
|
27
|
+
if (device_name == 'iOS Simulator' || device['simulator']) && !File.exists?(app_path)
|
27
28
|
raise "Can't find application at path #{app_path}"
|
28
29
|
end
|
29
30
|
app_path
|
@@ -44,23 +45,26 @@ module Zucchini
|
|
44
45
|
def self.device(device_name)
|
45
46
|
device_name ||= @@default_device_name
|
46
47
|
raise "Neither default device nor ZUCCHINI_DEVICE environment variable was set" unless device_name
|
47
|
-
raise "Device not listed in config.yml" unless (device = devices[device_name])
|
48
|
+
raise "Device '#{device_name}' not listed in config.yml" unless (device = devices[device_name])
|
48
49
|
{
|
49
|
-
:name
|
50
|
-
:udid
|
51
|
-
:screen
|
50
|
+
:name => device_name,
|
51
|
+
:udid => device['UDID'],
|
52
|
+
:screen => device['screen'],
|
53
|
+
:simulator => device['simulator'],
|
54
|
+
:orientation => device['orientation'] || 'portrait'
|
52
55
|
}
|
53
56
|
end
|
54
57
|
|
55
|
-
def self.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
def self.template
|
59
|
+
locations = [
|
60
|
+
`xcode-select -print-path`.gsub(/\n/, '') + "/Platforms/iPhoneOS.platform/Developer/Library/Instruments",
|
61
|
+
"/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents" # Xcode 4.5
|
62
|
+
].map do |start_path|
|
63
|
+
"#{start_path}/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
|
64
|
+
end
|
62
65
|
|
63
|
-
|
66
|
+
locations.each { |path| return path if File.exists?(path) }
|
67
|
+
raise "Can't find Instruments template (tried #{locations.join(', ')})"
|
64
68
|
end
|
65
69
|
end
|
66
70
|
end
|
data/lib/zucchini/detector.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
class Zucchini::Detector < Clamp::Command
|
2
2
|
attr_reader :features
|
3
|
-
|
3
|
+
|
4
4
|
parameter "PATH", "a path to feature or a directory"
|
5
|
-
|
5
|
+
|
6
6
|
def execute
|
7
7
|
raise "Directory #{path} does not exist" unless File.exists?(path)
|
8
8
|
|
@@ -10,14 +10,12 @@ class Zucchini::Detector < Clamp::Command
|
|
10
10
|
Zucchini::Config.base_path = File.exists?("#{path}/feature.zucchini") ? File.dirname(path) : path
|
11
11
|
|
12
12
|
@device = Zucchini::Config.device(ENV['ZUCCHINI_DEVICE'])
|
13
|
-
|
14
|
-
@template = detect_template
|
15
13
|
|
16
14
|
exit run_command
|
17
15
|
end
|
18
|
-
|
16
|
+
|
19
17
|
def run_command; end
|
20
|
-
|
18
|
+
|
21
19
|
def features
|
22
20
|
@features ||= detect_features(@path)
|
23
21
|
end
|
@@ -45,16 +43,4 @@ class Zucchini::Detector < Clamp::Command
|
|
45
43
|
def detection_error(path)
|
46
44
|
"#{path} is not a feature directory"
|
47
45
|
end
|
48
|
-
|
49
|
-
def detect_template
|
50
|
-
locations = [
|
51
|
-
`xcode-select -print-path`.gsub(/\n/, '') + "/Platforms/iPhoneOS.platform/Developer/Library/Instruments",
|
52
|
-
"/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents" # Xcode 4.5
|
53
|
-
].map do |start_path|
|
54
|
-
"#{start_path}/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
|
55
|
-
end
|
56
|
-
|
57
|
-
locations.each { |path| return path if File.exists?(path) }
|
58
|
-
raise "Can't find Instruments template (tried #{locations.join(', ')})"
|
59
|
-
end
|
60
|
-
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# FIXME: This needs to be refactored into a class (@vaskas).
|
2
|
+
|
3
|
+
module Zucchini
|
4
|
+
module Device
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def device_params(device)
|
9
|
+
if is_simulator?(device)
|
10
|
+
set_simulator_device(device)
|
11
|
+
''
|
12
|
+
else
|
13
|
+
"-w #{device[:udid]}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_simulator_device(device)
|
18
|
+
return unless device[:simulator].is_a?(String)
|
19
|
+
|
20
|
+
current_simulated_device = `defaults read com.apple.iphonesimulator "SimulateDevice"`.chomp
|
21
|
+
if current_simulated_device != device[:simulator]
|
22
|
+
simulator_pid = `ps ax|awk '/[i]Phone Simulator.app\\/Contents\\/MacOS\\/iPhone Simulator/{print $1}'`.chomp
|
23
|
+
Process.kill('INT', simulator_pid.to_i) unless simulator_pid.empty?
|
24
|
+
`defaults write com.apple.iphonesimulator "SimulateDevice" '"#{device[:simulator]}"'`
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def is_simulator?(device)
|
29
|
+
device[:name] == 'iOS Simulator' || device[:simulator]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/zucchini/feature.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
class Zucchini::Feature
|
2
|
+
include Zucchini::Compiler
|
3
|
+
include Zucchini::Device
|
4
|
+
|
2
5
|
attr_accessor :path
|
3
6
|
attr_accessor :device
|
4
|
-
attr_accessor :template
|
5
7
|
attr_accessor :stats
|
6
8
|
|
7
9
|
attr_reader :succeeded
|
@@ -45,32 +47,15 @@ class Zucchini::Feature
|
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
def compile_js
|
49
|
-
zucchini_base_path = File.expand_path(File.dirname(__FILE__))
|
50
|
-
|
51
|
-
feature_text = File.open("#{@path}/feature.zucchini").read.gsub(/\#.+[\z\n]?/,"").gsub(/\n/, "\\n")
|
52
|
-
File.open("#{run_data_path}/feature.coffee", "w+") { |f| f.write("Zucchini.run('#{feature_text}')") }
|
53
|
-
|
54
|
-
cs_paths = "#{zucchini_base_path}/uia #{@path}/../support/screens"
|
55
|
-
cs_paths += " #{@path}/../support/lib" if File.exists?("#{@path}/../support/lib")
|
56
|
-
cs_paths += " #{run_data_path}/feature.coffee"
|
57
|
-
|
58
|
-
compile_cmd = "coffee -o #{run_data_path} -j #{run_data_path}/feature.js -c #{cs_paths}"
|
59
|
-
system compile_cmd
|
60
|
-
unless $?.exitstatus == 0
|
61
|
-
raise "Error compiling a feature file: #{compile_cmd}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
50
|
def collect
|
66
51
|
with_setup do
|
67
52
|
`rm -rf #{run_data_path}/*`
|
68
|
-
compile_js
|
69
|
-
|
70
|
-
device_params = (@device[:name] == "iOS Simulator") ? "" : "-w #{@device[:udid]}"
|
71
53
|
|
72
54
|
begin
|
73
|
-
out = `instruments #{device_params
|
55
|
+
out = `instruments #{device_params(@device)} \
|
56
|
+
-t "#{Zucchini::Config.template}" "#{Zucchini::Config.app}" \
|
57
|
+
-e UIASCRIPT "#{compile_js(@device[:orientation])}" \
|
58
|
+
-e UIARESULTSPATH "#{run_data_path}" 2>&1`
|
74
59
|
puts out
|
75
60
|
# Hack. Instruments don't issue error return codes when JS exceptions occur
|
76
61
|
raise "Instruments run error" if (out.match /JavaScript error/) || (out.match /Instruments\ .{0,5}\ Error\ :/ )
|
data/lib/zucchini/runner.rb
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
UIAElement.prototype.$ = (name) ->
|
2
|
+
$.debug "element.$ is deprecated. Zucchini now supports mechanic.js selectors: https://github.com/jaykz52/mechanic"
|
3
|
+
$('#' + name).first()
|
4
|
+
|
5
|
+
waitForElement = (element) ->
|
6
|
+
$.debug "waitForElement is deprecated. Please use $.wait(finderFunction)"
|
7
|
+
$.wait(-> element)
|
8
|
+
|
9
|
+
extend = $.extend
|
@@ -0,0 +1,556 @@
|
|
1
|
+
/*
|
2
|
+
* mechanic.js UIAutomation Library
|
3
|
+
* http://cozykozy.com/pages/mechanicjs
|
4
|
+
*
|
5
|
+
* version e9320027e6b5250ef72990b314238a62a1c2db0a (master build)
|
6
|
+
*
|
7
|
+
* Copyright (c) 2012 Jason Kozemczak
|
8
|
+
* mechanic.js may be freely distributed under the MIT license.
|
9
|
+
*
|
10
|
+
* Includes parts of Zepto.js
|
11
|
+
* Copyright 2010-2012, Thomas Fuchs
|
12
|
+
* Zepto.js may be freely distributed under the MIT license.
|
13
|
+
*/
|
14
|
+
|
15
|
+
var mechanic = (function() {
|
16
|
+
// Save a reference to the local target for convenience
|
17
|
+
var target = UIATarget.localTarget();
|
18
|
+
|
19
|
+
// Set the default timeout value to 0 to avoid making walking the object tree incredibly slow.
|
20
|
+
// Developers can adjust this value by calling $.timeout(duration)
|
21
|
+
target.setTimeout(0);
|
22
|
+
|
23
|
+
var app = target.frontMostApp(),
|
24
|
+
window = app.mainWindow(),
|
25
|
+
emptyArray = [],
|
26
|
+
slice = emptyArray.slice
|
27
|
+
|
28
|
+
// Setup a map of UIAElement types to their "shortcut" selectors.
|
29
|
+
var typeShortcuts = {
|
30
|
+
'UIAActionSheet' : ['actionsheet'],
|
31
|
+
'UIAActivityIndicator' : ['activityIndicator'],
|
32
|
+
'UIAAlert' : ['alert'],
|
33
|
+
'UIAButton' : ['button'],
|
34
|
+
'UIACollectionCell' : ['collectionCell'],
|
35
|
+
'UIACollectionView' : ['collection'],
|
36
|
+
'UIAEditingMenu': ['editingMenu'],
|
37
|
+
'UIAElement' : ['\\*'], // TODO: sort of a hack
|
38
|
+
'UIAImage' : ['image'],
|
39
|
+
'UIAKey' : ['key'],
|
40
|
+
'UIAKeyboard' : ['keyboard'],
|
41
|
+
'UIALink' : ['link'],
|
42
|
+
'UIAPageIndicator' : ['pageIndicator'],
|
43
|
+
'UIAPicker' : ['picker'],
|
44
|
+
'UIAPickerWheel' : ['pickerwheel'],
|
45
|
+
'UIAPopover' : ['popover'],
|
46
|
+
'UIAProgressIndicator' : ['progress'],
|
47
|
+
'UIAScrollView' : ['scrollview'],
|
48
|
+
'UIASearchBar' : ['searchbar'],
|
49
|
+
'UIASecureTextField' : ['secure'],
|
50
|
+
'UIASegmentedControl' : ['segmented'],
|
51
|
+
'UIASlider' : ['slider'],
|
52
|
+
'UIAStaticText' : ['text'],
|
53
|
+
'UIAStatusBar' : ['statusbar'],
|
54
|
+
'UIASwitch' : ['switch'],
|
55
|
+
'UIATabBar' : ['tabbar'],
|
56
|
+
'UIATableView' : ['tableview'],
|
57
|
+
'UIATableCell' : ['cell', 'tableCell'],
|
58
|
+
'UIATableGroup' : ['group'],
|
59
|
+
'UIATextField' : ['textfield'],
|
60
|
+
'UIATextView' : ['textview'],
|
61
|
+
'UIAToolbar' : ['toolbar'],
|
62
|
+
'UIAWebView' : ['webview'],
|
63
|
+
'UIAWindow' : ['window'],
|
64
|
+
'UIANavigationBar': ['navigationBar']
|
65
|
+
};
|
66
|
+
|
67
|
+
// Build a RegExp for picking out type selectors.
|
68
|
+
var typeSelectorREString = (function() {
|
69
|
+
var key;
|
70
|
+
var typeSelectorREString = "\\";
|
71
|
+
for (key in typeShortcuts) {
|
72
|
+
typeSelectorREString += key + "|";
|
73
|
+
typeShortcuts[key].forEach(function(shortcut) { typeSelectorREString += shortcut + "|"; });
|
74
|
+
}
|
75
|
+
return typeSelectorREString.substr(1, typeSelectorREString.length - 2);
|
76
|
+
})();
|
77
|
+
|
78
|
+
var patternName = "[^,\\[\\]]+"
|
79
|
+
|
80
|
+
var selectorPatterns = {
|
81
|
+
simple: (new RegExp("^#("+patternName+")$"))
|
82
|
+
,byType: (new RegExp("^("+typeSelectorREString+")$"))
|
83
|
+
,byAttr: (new RegExp("^\\[(\\w+)=("+patternName+")\\]$"))
|
84
|
+
,byTypeAndAttr: (new RegExp("^("+typeSelectorREString+")\\[(\\w+)=("+patternName+")\\]$"))
|
85
|
+
,children: (new RegExp("^(.*) > (.*)$"))
|
86
|
+
,descendents: (new RegExp("^(.+) +(.+)$"))
|
87
|
+
}
|
88
|
+
|
89
|
+
var searches = {
|
90
|
+
simple: function(name) { return this.getElementsByName(name) }
|
91
|
+
,byType: function(type) { return this.getElementsByType(type) }
|
92
|
+
,byAttr: function(attr,value) { return this.getElementsByAttr(attr,value) }
|
93
|
+
,byTypeAndAttr: function(type,a,v) { return $(type, this).filter('['+a+'='+v+']') }
|
94
|
+
,children: function(parent,child) { return $(parent, this).children().filter(child) }
|
95
|
+
,descendents: function(parent,child) { return $(child, $(parent, this)) }
|
96
|
+
}
|
97
|
+
|
98
|
+
var filters = {
|
99
|
+
simple: function(name) { return this.name() == name }
|
100
|
+
,byType: function(type) { return this.isType(type) }
|
101
|
+
,byAttr: function(attr,value){ return this[attr] && this[attr]() == value }
|
102
|
+
,byTypeAndAttr: function(type,a,v ) { return this.isType(type) && this[a]() == v }
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
function Z(dom, selector){
|
107
|
+
dom = dom || emptyArray;
|
108
|
+
dom.__proto__ = Z.prototype;
|
109
|
+
dom.selector = selector || '';
|
110
|
+
if (dom === emptyArray) {
|
111
|
+
UIALogger.logWarning("element " + selector + " have not been found");
|
112
|
+
}
|
113
|
+
return dom;
|
114
|
+
}
|
115
|
+
|
116
|
+
function $(selector, context) {
|
117
|
+
if (!selector) return Z();
|
118
|
+
if (context !== undefined) return $(context).find(selector);
|
119
|
+
else if (selector instanceof Z) return selector;
|
120
|
+
else {
|
121
|
+
var dom;
|
122
|
+
if (isA(selector)) dom = compact(selector);
|
123
|
+
else if (selector instanceof UIAElement) dom = [selector];
|
124
|
+
else dom = $$(app, selector);
|
125
|
+
return Z(dom, selector);
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
$.qsa = $$ = function(element, selector) {
|
130
|
+
var ret = [],
|
131
|
+
groups = selector.split(/ *, */),
|
132
|
+
matches
|
133
|
+
$.each(groups, function() {
|
134
|
+
for (type in searches) {
|
135
|
+
if (matches = this.match(selectorPatterns[type])) {
|
136
|
+
matches.shift()
|
137
|
+
ret = ret.concat($(searches[type].apply(element, matches)))
|
138
|
+
break
|
139
|
+
}
|
140
|
+
}
|
141
|
+
})
|
142
|
+
return $(ret)
|
143
|
+
};
|
144
|
+
|
145
|
+
// Add functions to UIAElement to make object graph searching easier.
|
146
|
+
UIAElement.prototype.getElementsByName = function(name) {
|
147
|
+
return this.getElementsByAttr('name', name)
|
148
|
+
};
|
149
|
+
|
150
|
+
UIAElement.prototype.getElementsByAttr = function(attr, value) {
|
151
|
+
return $.map(this.elements().toArray(), function(el) {
|
152
|
+
var matches = el.getElementsByAttr(attr, value),
|
153
|
+
val = el[attr]
|
154
|
+
if (typeof val == 'function') val = val.apply(el)
|
155
|
+
if (typeof val != 'undefined' && val == value)
|
156
|
+
matches.push(el)
|
157
|
+
return matches
|
158
|
+
})
|
159
|
+
}
|
160
|
+
UIAElement.prototype.getElementsByType = function(type) {
|
161
|
+
return $.map(this.elements().toArray(), function(el) {
|
162
|
+
var matches = el.getElementsByType(type);
|
163
|
+
if (el.isType(type)) matches.unshift(el);
|
164
|
+
return matches;
|
165
|
+
});
|
166
|
+
};
|
167
|
+
UIAElement.prototype.isType = function(type) {
|
168
|
+
var thisType = this.toString().split(" ")[1];
|
169
|
+
thisType = thisType.substr(0, thisType.length - 1);
|
170
|
+
if (type === thisType) return true;
|
171
|
+
else if (typeShortcuts[thisType] !== undefined && typeShortcuts[thisType].indexOf(type) >= 0) return true;
|
172
|
+
else if (type === '*' || type === 'UIAElement') return true;
|
173
|
+
else return false;
|
174
|
+
};
|
175
|
+
|
176
|
+
function isF(value) { return ({}).toString.call(value) == "[object Function]"; }
|
177
|
+
function isO(value) { return value instanceof Object; }
|
178
|
+
function isA(value) { return value instanceof Array; }
|
179
|
+
function likeArray(obj) { return typeof obj.length == 'number'; }
|
180
|
+
|
181
|
+
function compact(array) { return array.filter(function(item){ return item !== undefined && item !== null; }); }
|
182
|
+
function flatten(array) { return array.length > 0 ? [].concat.apply([], array) : array; }
|
183
|
+
|
184
|
+
function uniq(array) { return array.filter(function(item,index,array){ return array.indexOf(item) == index; }); }
|
185
|
+
|
186
|
+
function filtered(elements, selector) {
|
187
|
+
return selector === undefined ? $(elements) : $(elements).filter(selector);
|
188
|
+
}
|
189
|
+
|
190
|
+
$.extend = function(target){
|
191
|
+
var key;
|
192
|
+
slice.call(arguments, 1).forEach(function(source) {
|
193
|
+
for (key in source) target[key] = source[key];
|
194
|
+
});
|
195
|
+
return target;
|
196
|
+
};
|
197
|
+
|
198
|
+
$.inArray = function(elem, array, i) {
|
199
|
+
return emptyArray.indexOf.call(array, elem, i);
|
200
|
+
};
|
201
|
+
|
202
|
+
$.map = function(elements, callback) {
|
203
|
+
var value, values = [], i, key;
|
204
|
+
if (likeArray(elements)) {
|
205
|
+
for (i = 0; i < elements.length; i++) {
|
206
|
+
value = callback(elements[i], i);
|
207
|
+
if (value != null) values.push(value);
|
208
|
+
}
|
209
|
+
} else {
|
210
|
+
for (key in elements) {
|
211
|
+
value = callback(elements[key], key);
|
212
|
+
if (value != null) values.push(value);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
return flatten(values);
|
216
|
+
};
|
217
|
+
|
218
|
+
$.each = function(elements, callback) {
|
219
|
+
var i, key;
|
220
|
+
if (likeArray(elements)) {
|
221
|
+
for(i = 0; i < elements.length; i++) {
|
222
|
+
if(callback.call(elements[i], i, elements[i]) === false) return elements;
|
223
|
+
}
|
224
|
+
} else {
|
225
|
+
for(key in elements) {
|
226
|
+
if(callback.call(elements[key], key, elements[key]) === false) return elements;
|
227
|
+
}
|
228
|
+
}
|
229
|
+
return elements;
|
230
|
+
};
|
231
|
+
|
232
|
+
$.fn = {
|
233
|
+
forEach: emptyArray.forEach,
|
234
|
+
reduce: emptyArray.reduce,
|
235
|
+
push: emptyArray.push,
|
236
|
+
indexOf: emptyArray.indexOf,
|
237
|
+
concat: emptyArray.concat,
|
238
|
+
map: function(fn){
|
239
|
+
return $.map(this, function(el, i){ return fn.call(el, i, el); });
|
240
|
+
},
|
241
|
+
slice: function(){
|
242
|
+
return $(slice.apply(this, arguments));
|
243
|
+
},
|
244
|
+
get: function(idx){ return idx === undefined ? slice.call(this) : this[idx]; },
|
245
|
+
size: function(){ return this.length; },
|
246
|
+
each: function(callback) {
|
247
|
+
this.forEach(function(el, idx){ callback.call(el, idx, el); });
|
248
|
+
return this;
|
249
|
+
},
|
250
|
+
filter: function(selector) {
|
251
|
+
var matches
|
252
|
+
for (type in filters) {
|
253
|
+
if (matches = selector.match(selectorPatterns[type])) {
|
254
|
+
matches.shift() // remove the original string, we only want the capture groups
|
255
|
+
return $.map(this, function(e) {
|
256
|
+
return filters[type].apply(e, matches) ? e : null
|
257
|
+
})
|
258
|
+
}
|
259
|
+
}
|
260
|
+
},
|
261
|
+
end: function(){
|
262
|
+
return this.prevObject || $();
|
263
|
+
},
|
264
|
+
andSelf:function(){
|
265
|
+
return this.add(this.prevObject || $());
|
266
|
+
},
|
267
|
+
add:function(selector,context){
|
268
|
+
return $(uniq(this.concat($(selector,context))));
|
269
|
+
},
|
270
|
+
is: function(selector){
|
271
|
+
return this.length > 0 && $(this[0]).filter(selector).length > 0;
|
272
|
+
},
|
273
|
+
not: function(selector){
|
274
|
+
var nodes=[];
|
275
|
+
if (isF(selector) && selector.call !== undefined)
|
276
|
+
this.each(function(idx){
|
277
|
+
if (!selector.call(this,idx)) nodes.push(this);
|
278
|
+
});
|
279
|
+
else {
|
280
|
+
var excludes = typeof selector == 'string' ? this.filter(selector) :
|
281
|
+
(likeArray(selector) && isF(selector.item)) ? slice.call(selector) : $(selector);
|
282
|
+
this.forEach(function(el){
|
283
|
+
if (excludes.indexOf(el) < 0) nodes.push(el);
|
284
|
+
});
|
285
|
+
}
|
286
|
+
return $(nodes);
|
287
|
+
},
|
288
|
+
eq: function(idx){
|
289
|
+
return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1);
|
290
|
+
},
|
291
|
+
first: function(){ var el = this[0]; return el && !isO(el) ? el : $(el); },
|
292
|
+
last: function(){ var el = this[this.length - 1]; return el && !isO(el) ? el : $(el); },
|
293
|
+
find: function(selector) {
|
294
|
+
var result;
|
295
|
+
if (this.length == 1) result = $$(this[0], selector);
|
296
|
+
else result = this.map(function(){ return $$(this, selector); });
|
297
|
+
return $(result);
|
298
|
+
},
|
299
|
+
predicate: function(predicate) {
|
300
|
+
return this.map(function(el, idx) {
|
301
|
+
if (typeof predicate == 'string') return el.withPredicate(predicate);
|
302
|
+
else return null;
|
303
|
+
});
|
304
|
+
},
|
305
|
+
valueForKey: function(key, value) {
|
306
|
+
var result = this.map(function(idx, el) {
|
307
|
+
if (key in el && el[key]() == value) {
|
308
|
+
return el;
|
309
|
+
}
|
310
|
+
return null;
|
311
|
+
});
|
312
|
+
return $(result);
|
313
|
+
},
|
314
|
+
valueInKey: function(key, val) {
|
315
|
+
var result = this.map(function(idx, el) {
|
316
|
+
if (key in el) {
|
317
|
+
var elKey = el[key]();
|
318
|
+
if (elKey === null) {
|
319
|
+
return null;
|
320
|
+
}
|
321
|
+
// make this a case insensitive search
|
322
|
+
elKey = elKey.toString().toLowerCase();
|
323
|
+
val = val.toString().toLowerCase();
|
324
|
+
|
325
|
+
if (elKey.indexOf(val) !== -1) {
|
326
|
+
return el;
|
327
|
+
}
|
328
|
+
}
|
329
|
+
return null;
|
330
|
+
});
|
331
|
+
return $(result);
|
332
|
+
},
|
333
|
+
closest: function(selector, context) {
|
334
|
+
var el = this[0], candidates = $$(context || app, selector);
|
335
|
+
if (!candidates.length) el = null;
|
336
|
+
while (el && candidates.indexOf(el) < 0)
|
337
|
+
el = el !== context && el !== app && el.parent();
|
338
|
+
return $(el);
|
339
|
+
},
|
340
|
+
ancestry: function(selector) {
|
341
|
+
var ancestors = [], elements = this;
|
342
|
+
while (elements.length > 0)
|
343
|
+
elements = $.map(elements, function(node){
|
344
|
+
if ((node = node.parent()) && !node.isType('UIAApplication') && ancestors.indexOf(node) < 0) {
|
345
|
+
ancestors.push(node);
|
346
|
+
return node;
|
347
|
+
}
|
348
|
+
});
|
349
|
+
return filtered(ancestors, selector);
|
350
|
+
},
|
351
|
+
parent: function(selector) {
|
352
|
+
return filtered(uniq(this.map(function() { return this.parent(); })), selector);
|
353
|
+
},
|
354
|
+
children: function(selector) {
|
355
|
+
return filtered(this.map(function(){ return slice.call(this.elements()); }), selector);
|
356
|
+
},
|
357
|
+
siblings: function(selector) {
|
358
|
+
return filtered(this.map(function(i, el) {
|
359
|
+
return slice.call(el.parent().elements()).filter(function(child){ return child!==el; });
|
360
|
+
}), selector);
|
361
|
+
},
|
362
|
+
next: function(selector) {
|
363
|
+
return filtered(this.map(function() {
|
364
|
+
var els = this.parent().elements().toArray();
|
365
|
+
return els[els.indexOf(this) + 1];
|
366
|
+
}), selector);
|
367
|
+
},
|
368
|
+
prev: function(selector) {
|
369
|
+
return filtered(this.map(function() {
|
370
|
+
var els = this.parent().elements().toArray();
|
371
|
+
return els[els.indexOf(this) - 1];
|
372
|
+
}), selector);
|
373
|
+
},
|
374
|
+
index: function(element) {
|
375
|
+
return element ? this.indexOf($(element)[0]) : this.parent().elements().toArray().indexOf(this[0]);
|
376
|
+
},
|
377
|
+
pluck: function(property) {
|
378
|
+
return this.map(function() {
|
379
|
+
if (typeof this[property] == 'function') return this[property]();
|
380
|
+
else return this[property];
|
381
|
+
});
|
382
|
+
}
|
383
|
+
};
|
384
|
+
|
385
|
+
'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property) {
|
386
|
+
var fn = $.fn[property];
|
387
|
+
$.fn[property] = function() {
|
388
|
+
var ret = fn.apply(this, arguments);
|
389
|
+
ret.prevObject = this;
|
390
|
+
return ret;
|
391
|
+
};
|
392
|
+
});
|
393
|
+
|
394
|
+
Z.prototype = $.fn;
|
395
|
+
return $;
|
396
|
+
})();
|
397
|
+
|
398
|
+
var $ = $ || mechanic; // expose $ shortcut
|
399
|
+
// mechanic.js
|
400
|
+
// Copyright (c) 2012 Jason Kozemczak
|
401
|
+
// mechanic.js may be freely distributed under the MIT license.
|
402
|
+
|
403
|
+
(function($) {
|
404
|
+
$.extend($, {
|
405
|
+
log: function(s, level) {
|
406
|
+
level = level || 'message';
|
407
|
+
if (level === 'error') $.error(s);
|
408
|
+
else if (level === 'warn') $.warn(s);
|
409
|
+
else if (level === 'debug') $.debug(s);
|
410
|
+
else $.message(s);
|
411
|
+
},
|
412
|
+
error: function(s) { UIALogger.logError(s); },
|
413
|
+
warn: function(s) { UIALogger.logWarning(s); },
|
414
|
+
debug: function(s) { UIALogger.logDebug(s); },
|
415
|
+
message: function(s) { UIALogger.logMessage(s); },
|
416
|
+
capture: function(imageName, rect) {
|
417
|
+
var target = UIATarget.localTarget();
|
418
|
+
imageName = imageName || new Date().toString();
|
419
|
+
if (rect) target.captureRectWithName(rect, imageName);
|
420
|
+
else target.captureScreenWithName(imageName);
|
421
|
+
}
|
422
|
+
});
|
423
|
+
|
424
|
+
$.extend($.fn, {
|
425
|
+
log: function() { return this.each(function() { this.logElement(); }); },
|
426
|
+
logTree: function () { return this.each(function() { this.logElementTree(); }); },
|
427
|
+
capture: function(imageName) {
|
428
|
+
imageName = imageName || new Date().toString();
|
429
|
+
return this.each(function() { $.capture(imageName + '-' + this.name(), this.rect()); });
|
430
|
+
}
|
431
|
+
});
|
432
|
+
})(mechanic);
|
433
|
+
// mechanic.js
|
434
|
+
// Copyright (c) 2012 Jason Kozemczak
|
435
|
+
// mechanic.js may be freely distributed under the MIT license.
|
436
|
+
|
437
|
+
(function($) {
|
438
|
+
var app = UIATarget.localTarget().frontMostApp();
|
439
|
+
$.extend($.fn, {
|
440
|
+
name: function() { return (this.length > 0) ? this[0].name() : null; },
|
441
|
+
label: function() { return (this.length > 0) ? this[0].label() : null; },
|
442
|
+
value: function() { return (this.length > 0) ? this[0].value() : null; },
|
443
|
+
isFocused: function() { return (this.length > 0) ? this[0].hasKeyboardFocus() : false; },
|
444
|
+
isEnabled: function() { return (this.length > 0) ? this[0].isEnabled() : false; },
|
445
|
+
isVisible: function() { return (this.length > 0) ? this[0].isVisible() : false; },
|
446
|
+
isValid: function(certain) {
|
447
|
+
if (this.length != 1) return false;
|
448
|
+
else if (certain) return this[0].checkIsValid();
|
449
|
+
else return this[0].isValid();
|
450
|
+
}
|
451
|
+
});
|
452
|
+
|
453
|
+
$.extend($, {
|
454
|
+
version: function() {
|
455
|
+
return app.version();
|
456
|
+
},
|
457
|
+
bundleID: function() {
|
458
|
+
return app.bundleID();
|
459
|
+
},
|
460
|
+
prefs: function(prefsOrKey) {
|
461
|
+
// TODO: should we handle no-arg version that returns all prefs???
|
462
|
+
if (typeof prefsOrKey == 'string') return app.preferencesValueForKey(prefsOrKey);
|
463
|
+
else {
|
464
|
+
$.each(prefsOrKey, function(key, val) {
|
465
|
+
app.setPreferencesValueForKey(val, key);
|
466
|
+
});
|
467
|
+
}
|
468
|
+
}
|
469
|
+
});
|
470
|
+
|
471
|
+
})(mechanic);
|
472
|
+
// mechanic.js
|
473
|
+
// Copyright (c) 2012 Jason Kozemczak
|
474
|
+
// mechanic.js may be freely distributed under the MIT license.
|
475
|
+
|
476
|
+
(function($) {
|
477
|
+
var target = UIATarget.localTarget();
|
478
|
+
$.extend($, {
|
479
|
+
timeout: function(duration) { target.setTimeout(duration); },
|
480
|
+
delay: function(seconds) { target.delay(seconds); },
|
481
|
+
cmd: function(path, args, timeout) { target.host().performTaskWithPathArgumentsTimeout(path, args, timeout); },
|
482
|
+
orientation: function(orientation) {
|
483
|
+
if (orientation === undefined || orientation === null) return target.deviceOrientation();
|
484
|
+
else target.setDeviceOrientation(orientation);
|
485
|
+
},
|
486
|
+
location: function(coordinates, options) {
|
487
|
+
options = options || {};
|
488
|
+
target.setLocationWithOptions(coordinates, options);
|
489
|
+
},
|
490
|
+
shake: function() { target.shake(); },
|
491
|
+
rotate: function(options) { target.rotateWithOptions(options); },
|
492
|
+
pinchScreen: function(options) {
|
493
|
+
if (!options.style) options.style = 'open';
|
494
|
+
if (options.style === 'close') target.pinchCloseFromToForDuration(options.from, options.to, options.duration);
|
495
|
+
else target.pinchOpenFromToForDuration(options.from, options.to, options.duration);
|
496
|
+
},
|
497
|
+
drag: function(options) { target.dragFromToForDuration(options.from, options.to, options.duration); },
|
498
|
+
flick: function(options) { target.flickFromTo(options.from, options.to); },
|
499
|
+
lock: function(duration) { target.lockForDuration(duration); },
|
500
|
+
backgroundApp: function(duration) { target.deactivateAppForDuration(duration); },
|
501
|
+
volume: function(direction, duration) {
|
502
|
+
if (direction === 'up') {
|
503
|
+
if (duration) target.holdVolumeUp(duration);
|
504
|
+
else target.clickVolumeUp();
|
505
|
+
} else {
|
506
|
+
if (duration) target.holdVolumeDown(duration);
|
507
|
+
else target.clickVolumeDown();
|
508
|
+
}
|
509
|
+
},
|
510
|
+
input: function(s) {
|
511
|
+
target.frontMostApp().keyboard().typeString(s);
|
512
|
+
}
|
513
|
+
});
|
514
|
+
|
515
|
+
$.extend($.fn, {
|
516
|
+
tap: function(options) {
|
517
|
+
options = options || {};
|
518
|
+
return this.each(function() {
|
519
|
+
// TODO: tapWithOptions supports most of the behavior of doubleTap/twoFingerTap looking at the API, do we need to support these methods??
|
520
|
+
if (options.style === 'double') this.doubleTap();
|
521
|
+
else if (options.style === 'twoFinger') this.twoFingerTap();
|
522
|
+
else this.tapWithOptions(options);
|
523
|
+
});
|
524
|
+
},
|
525
|
+
touch: function(duration) {
|
526
|
+
return this.each(function() { this.touchAndHold(duration); });
|
527
|
+
},
|
528
|
+
dragInside: function(options) {
|
529
|
+
return this.each(function() { this.dragInsideWithOptions(options); });
|
530
|
+
},
|
531
|
+
flick: function(options) {
|
532
|
+
return this.each(function() { this.flickInsideWithOptions(options); });
|
533
|
+
},
|
534
|
+
rotate: function(options) {
|
535
|
+
return this.each(function() { this.rotateWithOptions(options); });
|
536
|
+
},
|
537
|
+
scrollToVisible: function() {
|
538
|
+
if (this.length > 0) this[0].scrollToVisible();
|
539
|
+
return this;
|
540
|
+
},
|
541
|
+
input: function(s) {
|
542
|
+
if (this.length > 0) {
|
543
|
+
this[0].tap();
|
544
|
+
$.input(s);
|
545
|
+
}
|
546
|
+
}
|
547
|
+
});
|
548
|
+
|
549
|
+
'delay,cmd,orientation,location,shake,pinchScreen,drag,lock,backgroundApp,volume'.split(',').forEach(function(property) {
|
550
|
+
var fn = $[property];
|
551
|
+
$.fn[property] = function() {
|
552
|
+
fn.apply($, arguments);
|
553
|
+
return this;
|
554
|
+
};
|
555
|
+
});
|
556
|
+
})(mechanic);
|