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 CHANGED
@@ -1,6 +1,16 @@
1
1
  language: ruby
2
+
3
+ env:
4
+ - COVERAGE=coveralls
5
+
2
6
  rvm:
3
7
  - 1.9.3
4
- - 1.9.2
8
+ - 2.0.0
9
+ - rbx-19mode
10
+
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: rbx-19mode
14
+
5
15
  before_install:
6
16
  - npm install coffee-script
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
@@ -4,6 +4,8 @@ module Zucchini
4
4
  require 'zucchini/config'
5
5
  require 'zucchini/screenshot'
6
6
  require 'zucchini/report'
7
+ require 'zucchini/compiler'
8
+ require 'zucchini/device'
7
9
  require 'zucchini/feature'
8
10
  require 'zucchini/detector'
9
11
  require 'zucchini/runner'
@@ -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
@@ -20,10 +20,11 @@ module Zucchini
20
20
  end
21
21
 
22
22
  def self.app
23
- device_name = ENV['ZUCCHINI_DEVICE'] || @@default_device_name
24
- app_path = File.absolute_path(devices[device_name]['app'] || @@config['app'] || ENV['ZUCCHINI_APP'])
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 => device_name,
50
- :udid => device['UDID'],
51
- :screen => device['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.server(server_name)
56
- @@config['servers'][server_name]
57
- end
58
-
59
- def self.url(server_name, href="")
60
- server_config = server(server_name)
61
- port = server_config['port'] ? ":#{server_config['port']}" : ""
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
- "http://#{server_config['host']}#{port}#{href}"
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
@@ -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
@@ -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} -t "#{@template}" "#{Zucchini::Config.app}" -e UIASCRIPT "#{run_data_path}/feature.js" -e UIARESULTSPATH "#{run_data_path}" 2>&1`
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\ :/ )
@@ -11,8 +11,7 @@ class Zucchini::Runner < Zucchini::Detector
11
11
  compare_threads = {}
12
12
 
13
13
  features.each do |f|
14
- f.device = @device
15
- f.template = @template
14
+ f.device = @device
16
15
 
17
16
  if collect? then f.collect
18
17
  elsif compare? then f.compare
@@ -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);