run_loop 2.1.0.pre1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/run_loop.rb +48 -44
- data/lib/run_loop/app.rb +83 -1
- data/lib/run_loop/cli/simctl.rb +8 -3
- data/lib/run_loop/core.rb +2 -0
- data/lib/run_loop/core_simulator.rb +54 -15
- data/lib/run_loop/environment.rb +22 -1
- data/lib/run_loop/http/retriable_client.rb +5 -1
- data/lib/run_loop/instruments.rb +3 -30
- data/lib/run_loop/ipa.rb +30 -1
- data/lib/run_loop/simctl.rb +89 -0
- data/lib/run_loop/version.rb +1 -1
- data/lib/run_loop/xcuitest.rb +313 -107
- metadata +5 -5
- data/lib/run_loop/simctl/plists.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee9ecaf4f1c6ec33be1a4e7640991c46e14f1938
|
4
|
+
data.tar.gz: 7348a649fd00ef921ccf16d049ca1f7c9c9b5421
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2de3db8241ef24056e0d140d569c30082d0436acd251134b0b2908ef063a9dc97e9be2be8431701340c21e80bc3cd2978a41893ffb3b787231aaff191b8637f
|
7
|
+
data.tar.gz: 46c3ca51600d39434fee5a25d47b41c1122846f5e7eb3852e079174c1393acfa18df5e0cb9605899d6341b7cad1e3846212ad8348de1be1aad13af2622555010
|
data/lib/run_loop.rb
CHANGED
@@ -31,7 +31,7 @@ require 'run_loop/cache/cache'
|
|
31
31
|
require 'run_loop/host_cache'
|
32
32
|
require 'run_loop/patches/awesome_print'
|
33
33
|
require 'run_loop/core_simulator'
|
34
|
-
require
|
34
|
+
require "run_loop/simctl"
|
35
35
|
require 'run_loop/template'
|
36
36
|
require "run_loop/locale"
|
37
37
|
require "run_loop/language"
|
@@ -66,64 +66,69 @@ module RunLoop
|
|
66
66
|
|
67
67
|
def self.run(options={})
|
68
68
|
|
69
|
-
if
|
70
|
-
|
69
|
+
if options[:xcuitest]
|
70
|
+
RunLoop::XCUITest.run(options)
|
71
|
+
else
|
72
|
+
|
73
|
+
if RunLoop::Instruments.new.instruments_app_running?
|
74
|
+
raise %q(The Instruments.app is open.
|
71
75
|
|
72
76
|
If the Instruments.app is open, the instruments command line tool cannot take
|
73
77
|
control of your application.
|
74
78
|
|
75
79
|
Please quit the Instruments.app and try again.)
|
76
80
|
|
77
|
-
|
81
|
+
end
|
78
82
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
else
|
83
|
-
if uia_strategy
|
84
|
-
script = default_script_for_uia_strategy(uia_strategy)
|
83
|
+
uia_strategy = options[:uia_strategy]
|
84
|
+
if options[:script]
|
85
|
+
script = validate_script(options[:script])
|
85
86
|
else
|
86
|
-
if
|
87
|
-
uia_strategy = :host
|
88
|
-
script = Core.script_for_key(:run_loop_host)
|
89
|
-
else
|
90
|
-
uia_strategy = :preferences
|
87
|
+
if uia_strategy
|
91
88
|
script = default_script_for_uia_strategy(uia_strategy)
|
89
|
+
else
|
90
|
+
if options[:calabash_lite]
|
91
|
+
uia_strategy = :host
|
92
|
+
script = Core.script_for_key(:run_loop_host)
|
93
|
+
else
|
94
|
+
uia_strategy = :preferences
|
95
|
+
script = default_script_for_uia_strategy(uia_strategy)
|
96
|
+
end
|
92
97
|
end
|
93
98
|
end
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
99
|
+
# At this point, 'script' has been chosen, but uia_strategy might not
|
100
|
+
unless uia_strategy
|
101
|
+
desired_script = options[:script]
|
102
|
+
if desired_script.is_a?(String) #custom path to script
|
103
|
+
uia_strategy = :host
|
104
|
+
elsif desired_script == :run_loop_host
|
105
|
+
uia_strategy = :host
|
106
|
+
elsif desired_script == :run_loop_fast_uia
|
107
|
+
uia_strategy = :preferences
|
108
|
+
elsif desired_script == :run_loop_shared_element
|
109
|
+
uia_strategy = :shared_element
|
110
|
+
else
|
111
|
+
raise "Inconsistent state: desired script #{desired_script} has not uia_strategy"
|
112
|
+
end
|
108
113
|
end
|
109
|
-
end
|
110
114
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
+
# At this point script and uia_strategy selected
|
116
|
+
cloned_options = options.clone
|
117
|
+
cloned_options[:script] = script
|
118
|
+
cloned_options[:uia_strategy] = uia_strategy
|
115
119
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
120
|
+
# Xcode and SimControl will not be properly cloned and we don't want
|
121
|
+
# them to be; we want to use the exact objects that were passed.
|
122
|
+
if options[:xcode]
|
123
|
+
cloned_options[:xcode] = options[:xcode]
|
124
|
+
end
|
121
125
|
|
122
|
-
|
123
|
-
|
124
|
-
|
126
|
+
if options[:sim_control]
|
127
|
+
cloned_options[:sim_control] = options[:sim_control]
|
128
|
+
end
|
125
129
|
|
126
|
-
|
130
|
+
Core.run_with_options(cloned_options)
|
131
|
+
end
|
127
132
|
end
|
128
133
|
|
129
134
|
def self.send_command(run_loop, cmd, options={timeout: 60}, num_retries=0, last_error=nil)
|
@@ -235,5 +240,4 @@ Please quit the Instruments.app and try again.)
|
|
235
240
|
def self.log_info(*args)
|
236
241
|
RunLoop::Logging.log_info(*args)
|
237
242
|
end
|
238
|
-
|
239
243
|
end
|
data/lib/run_loop/app.rb
CHANGED
@@ -33,7 +33,20 @@ Bundle must:
|
|
33
33
|
|
34
34
|
# @!visibility private
|
35
35
|
def to_s
|
36
|
-
|
36
|
+
cf_bundle_version = bundle_version
|
37
|
+
cf_bundle_short_version = short_bundle_version
|
38
|
+
|
39
|
+
if cf_bundle_version && cf_bundle_short_version
|
40
|
+
version = "#{cf_bundle_version.to_s} / #{cf_bundle_short_version}"
|
41
|
+
elsif cf_bundle_version
|
42
|
+
version = cf_bundle_version.to_s
|
43
|
+
elsif cf_bundle_short_version
|
44
|
+
version = cf_bundle_short_version
|
45
|
+
else
|
46
|
+
version = ""
|
47
|
+
end
|
48
|
+
|
49
|
+
"#<APP #{bundle_identifier} #{version} #{path}>"
|
37
50
|
end
|
38
51
|
|
39
52
|
# @!visibility private
|
@@ -130,6 +143,75 @@ Bundle must:
|
|
130
143
|
RunLoop::Codesign.distribution?(path)
|
131
144
|
end
|
132
145
|
|
146
|
+
# Returns the CFBundleShortVersionString of the app as Version instance.
|
147
|
+
#
|
148
|
+
# Apple docs:
|
149
|
+
#
|
150
|
+
# CFBundleShortVersionString specifies the release version number of the
|
151
|
+
# bundle, which identifies a released iteration of the app. The release
|
152
|
+
# version number is a string comprised of three period-separated integers.
|
153
|
+
#
|
154
|
+
# The first integer represents major revisions to the app, such as revisions
|
155
|
+
# that implement new features or major changes. The second integer denotes
|
156
|
+
# revisions that implement less prominent features. The third integer
|
157
|
+
# represents maintenance releases.
|
158
|
+
#
|
159
|
+
# The value for this key differs from the value for CFBundleVersion, which
|
160
|
+
# identifies an iteration (released or unreleased) of the app. This key can
|
161
|
+
# be localized by including it in your InfoPlist.strings files.
|
162
|
+
#
|
163
|
+
# @return [RunLoop::Version, nil] Returns a Version instance if the
|
164
|
+
# CFBundleShortVersion string is well formed and nil if not.
|
165
|
+
def marketing_version
|
166
|
+
string = plist_buddy.plist_read("CFBundleShortVersionString", info_plist_path)
|
167
|
+
begin
|
168
|
+
version = RunLoop::Version.new(string)
|
169
|
+
rescue
|
170
|
+
if string && string != ""
|
171
|
+
RunLoop.log_debug("CFBundleShortVersionString: '#{string}' is not a well formed version string")
|
172
|
+
else
|
173
|
+
RunLoop.log_debug("CFBundleShortVersionString is not defined in Info.plist")
|
174
|
+
end
|
175
|
+
version = nil
|
176
|
+
end
|
177
|
+
version
|
178
|
+
end
|
179
|
+
|
180
|
+
# See #marketing_version
|
181
|
+
alias_method :short_bundle_version, :marketing_version
|
182
|
+
|
183
|
+
# Returns the CFBundleVersionString of the app as Version instance.
|
184
|
+
#
|
185
|
+
# Apple docs:
|
186
|
+
#
|
187
|
+
# CFBundleVersion specifies the build version number of the bundle, which
|
188
|
+
# identifies an iteration (released or unreleased) of the bundle. The build
|
189
|
+
# version number should be a string comprised of three non-negative,
|
190
|
+
# period-separated integers with the first integer being greater than zero.
|
191
|
+
# The string should only contain numeric (0-9) and period (.) characters.
|
192
|
+
# Leading zeros are truncated from each integer and will be ignored (that
|
193
|
+
# is, 1.02.3 is equivalent to 1.2.3).
|
194
|
+
#
|
195
|
+
# @return [RunLoop::Version, nil] Returns a Version instance if the
|
196
|
+
# CFBundleVersion string is well formed and nil if not.
|
197
|
+
def build_version
|
198
|
+
string = plist_buddy.plist_read("CFBundleVersionString", info_plist_path)
|
199
|
+
begin
|
200
|
+
version = RunLoop::Version.new(string)
|
201
|
+
rescue
|
202
|
+
if string && string != ""
|
203
|
+
RunLoop.log_debug("CFBundleVersionString: '#{string}' is not a well formed version string")
|
204
|
+
else
|
205
|
+
RunLoop.log_debug("CFBundleVersionString is not defined in Info.plist")
|
206
|
+
end
|
207
|
+
version = nil
|
208
|
+
end
|
209
|
+
version
|
210
|
+
end
|
211
|
+
|
212
|
+
# See #build_version
|
213
|
+
alias_method :bundle_version, :build_version
|
214
|
+
|
133
215
|
# @!visibility private
|
134
216
|
# Collects the paths to executables in the bundle.
|
135
217
|
def executables
|
data/lib/run_loop/cli/simctl.rb
CHANGED
@@ -56,20 +56,25 @@ module RunLoop
|
|
56
56
|
debug = options[:debug]
|
57
57
|
device = options[:device]
|
58
58
|
|
59
|
+
manage_processes
|
60
|
+
|
59
61
|
if device
|
60
62
|
RunLoop::Environment.with_debugging(debug) do
|
63
|
+
RunLoop::CoreSimulator.erase(device)
|
61
64
|
launch_simulator(device, xcode)
|
62
65
|
end
|
63
66
|
else
|
64
|
-
|
67
|
+
RunLoop::Environment.with_debugging(debug) do
|
68
|
+
erase_and_launch_each_simulator
|
69
|
+
end
|
65
70
|
end
|
66
71
|
|
67
|
-
manage_processes
|
68
72
|
end
|
69
73
|
|
70
74
|
no_commands do
|
71
|
-
def
|
75
|
+
def erase_and_launch_each_simulator
|
72
76
|
sim_control.simulators.each do |simulator|
|
77
|
+
RunLoop::CoreSimulator.erase(simulator)
|
73
78
|
launch_simulator(simulator, xcode)
|
74
79
|
end
|
75
80
|
end
|
data/lib/run_loop/core.rb
CHANGED
@@ -676,6 +676,8 @@ Logfile: #{log_file}
|
|
676
676
|
#
|
677
677
|
# 1. enabling accessibility and software keyboard
|
678
678
|
# 2. installing / uninstalling apps
|
679
|
+
#
|
680
|
+
# TODO: move to CoreSimulator?
|
679
681
|
def self.prepare_simulator(app, device, xcode, simctl, reset_options)
|
680
682
|
|
681
683
|
# Validate the architecture.
|
@@ -57,7 +57,7 @@ class RunLoop::CoreSimulator
|
|
57
57
|
# This process is a daemon, and requires 'KILL' to terminate.
|
58
58
|
# Killing the process is fast, but it takes a long time to
|
59
59
|
# restart.
|
60
|
-
|
60
|
+
['com.apple.CoreSimulator.CoreSimulatorService', false],
|
61
61
|
|
62
62
|
# Probably do not need to quit this, but it is tempting to do so.
|
63
63
|
#['com.apple.CoreSimulator.SimVerificationService', false],
|
@@ -94,6 +94,12 @@ class RunLoop::CoreSimulator
|
|
94
94
|
# launchd_sim process.
|
95
95
|
['launchd_sim', false],
|
96
96
|
|
97
|
+
# Required for XCUITest termination; the simulator hangs otherwise.
|
98
|
+
["xpcproxy", false],
|
99
|
+
|
100
|
+
# Causes crash reports on Xcode < 7.0
|
101
|
+
["apsd", true],
|
102
|
+
|
97
103
|
# assetsd instances clobber each other and are not properly
|
98
104
|
# killed when quiting the simulator.
|
99
105
|
['assetsd', false],
|
@@ -167,7 +173,7 @@ class RunLoop::CoreSimulator
|
|
167
173
|
#
|
168
174
|
# @param [RunLoop::Device] simulator The simulator to erase
|
169
175
|
# @param [Hash] options Control the behavior of the method.
|
170
|
-
# @option options [Numeric] :
|
176
|
+
# @option options [Numeric] :timeout (180) How long tow wait for simctl to
|
171
177
|
# shutdown and erase the simulator. The timeout is apply separately to
|
172
178
|
# each command.
|
173
179
|
#
|
@@ -393,25 +399,46 @@ $ bundle exec run-loop simctl manage-processes
|
|
393
399
|
# relaunch it.
|
394
400
|
launch_simulator
|
395
401
|
|
396
|
-
|
397
|
-
|
398
|
-
hash = xcrun.exec(args, log_cmd: true, timeout: timeout)
|
402
|
+
tries = RunLoop::Environment.ci? ? 5 : 3
|
403
|
+
last_error = nil
|
399
404
|
|
400
|
-
|
405
|
+
RunLoop.log_debug("Trying #{tries} times to launch #{app.bundle_identifier} on #{device}")
|
401
406
|
|
402
|
-
|
403
|
-
|
404
|
-
|
407
|
+
tries.times do
|
408
|
+
hash = launch_app_with_simctl
|
409
|
+
exit_status = hash[:exit_status]
|
410
|
+
if exit_status != 0
|
411
|
+
RunLoop.log_debug("Failed to launch app.")
|
412
|
+
out.split($-0).each do |line|
|
413
|
+
RunLoop.log_debug(" #{line}")
|
414
|
+
end
|
415
|
+
# Simulator is probably in a bad state, but this will be super disruptive.
|
416
|
+
# Let's try a softer approach first - sleep.
|
417
|
+
# self.terminate_core_simulator_processes
|
418
|
+
sleep(0.5)
|
419
|
+
last_error = out
|
420
|
+
else
|
421
|
+
last_error = nil
|
422
|
+
break
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
if last_error
|
427
|
+
raise RuntimeError, %Q[Could not launch #{app.bundle_identifier} on #{device}
|
428
|
+
|
429
|
+
#{last_error}
|
430
|
+
|
431
|
+
]
|
405
432
|
end
|
406
433
|
|
407
434
|
options = {
|
408
|
-
|
409
|
-
|
435
|
+
:timeout => 10,
|
436
|
+
:raise_on_timeout => true
|
410
437
|
}
|
411
438
|
|
412
439
|
RunLoop::ProcessWaiter.new(app.executable_name, options).wait_for_any
|
413
|
-
|
414
440
|
device.simulator_wait_for_stable_state
|
441
|
+
|
415
442
|
true
|
416
443
|
end
|
417
444
|
|
@@ -588,6 +615,13 @@ Command had no output
|
|
588
615
|
installed_app_bundle_dir
|
589
616
|
end
|
590
617
|
|
618
|
+
# @!visibility private
|
619
|
+
def launch_app_with_simctl
|
620
|
+
args = ['simctl', 'launch', device.udid, app.bundle_identifier]
|
621
|
+
timeout = DEFAULT_OPTIONS[:launch_app_timeout]
|
622
|
+
xcrun.exec(args, log_cmd: true, timeout: timeout)
|
623
|
+
end
|
624
|
+
|
591
625
|
# Required for support of iOS 7 CoreSimulators. Can be removed when
|
592
626
|
# Xcode support is dropped.
|
593
627
|
def sdk_gte_8?
|
@@ -716,15 +750,20 @@ Command had no output
|
|
716
750
|
def installed_app_bundle_dir
|
717
751
|
sim_app_dir = device_applications_dir
|
718
752
|
return nil if !File.exist?(sim_app_dir)
|
719
|
-
|
720
|
-
|
753
|
+
|
754
|
+
if xcode.version_gte_7?
|
755
|
+
simctl = RunLoop::Simctl.new(device)
|
756
|
+
simctl.app_container(app.bundle_identifier)
|
757
|
+
else
|
758
|
+
Dir.glob("#{sim_app_dir}/**/*.app").find do |path|
|
759
|
+
RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
|
760
|
+
end
|
721
761
|
end
|
722
762
|
end
|
723
763
|
|
724
764
|
# 1. Does nothing if the app is not installed.
|
725
765
|
# 2. Does nothing if the app the same as the app that is installed
|
726
766
|
# 3. Installs app if it is different from the installed app
|
727
|
-
#
|
728
767
|
def ensure_app_same
|
729
768
|
installed_app_bundle = installed_app_bundle_dir
|
730
769
|
|
data/lib/run_loop/environment.rb
CHANGED
@@ -229,6 +229,27 @@ module RunLoop
|
|
229
229
|
value = ENV["CI"]
|
230
230
|
!!value && value != ''
|
231
231
|
end
|
232
|
+
|
233
|
+
# !@visibility private
|
234
|
+
# Returns the value of CBXWS. This can be used to override the default
|
235
|
+
# CBXDriver.xcworkspace. You should only set this if you are actively
|
236
|
+
# developing the CBXDriver.
|
237
|
+
def self.cbxws
|
238
|
+
value = ENV["CBXWS"]
|
239
|
+
if value.nil? || value == ""
|
240
|
+
nil
|
241
|
+
else
|
242
|
+
path = File.expand_path(value)
|
243
|
+
if !File.directory?(path)
|
244
|
+
raise RuntimeError, %Q[CBXWS is set, but there is no workspace at
|
245
|
+
#{path}
|
246
|
+
|
247
|
+
Only set CBXWS if you are developing new features in the CBXRunner.
|
248
|
+
|
249
|
+
Check your environment.]
|
250
|
+
end
|
251
|
+
path
|
252
|
+
end
|
253
|
+
end
|
232
254
|
end
|
233
255
|
end
|
234
|
-
|
@@ -101,6 +101,10 @@ module RunLoop
|
|
101
101
|
request(request, :post, options)
|
102
102
|
end
|
103
103
|
|
104
|
+
def delete(request, options={})
|
105
|
+
request(request, :delete, options)
|
106
|
+
end
|
107
|
+
|
104
108
|
private
|
105
109
|
|
106
110
|
def request(request, request_method, options={})
|
@@ -134,7 +138,7 @@ module RunLoop
|
|
134
138
|
return client.send(request_method, @server.endpoint + request.route,
|
135
139
|
request.params, header)
|
136
140
|
rescue *RETRY_ON => e
|
137
|
-
RunLoop.log_debug("Rescued http error: #{e}")
|
141
|
+
#RunLoop.log_debug("Rescued http error: #{e}")
|
138
142
|
|
139
143
|
if first_try
|
140
144
|
if @on_error[e.class]
|
data/lib/run_loop/instruments.rb
CHANGED
@@ -124,15 +124,11 @@ module RunLoop
|
|
124
124
|
# Send a kill signal to any running `instruments` processes.
|
125
125
|
#
|
126
126
|
# Only one instruments process can be running at any one time.
|
127
|
-
|
128
|
-
# @param [RunLoop::Xcode] xcode Used to make check the
|
129
|
-
# active Xcode version.
|
130
|
-
def kill_instruments(xcode = RunLoop::Xcode.new)
|
131
|
-
kill_signal = kill_signal(xcode)
|
127
|
+
def kill_instruments(_=nil)
|
132
128
|
instruments_pids.each do |pid|
|
133
|
-
terminator = RunLoop::ProcessTerminator.new(pid,
|
129
|
+
terminator = RunLoop::ProcessTerminator.new(pid, "QUIT", "instruments")
|
134
130
|
unless terminator.kill_process
|
135
|
-
terminator = RunLoop::ProcessTerminator.new(pid,
|
131
|
+
terminator = RunLoop::ProcessTerminator.new(pid, "KILL", "instruments")
|
136
132
|
terminator.kill_process
|
137
133
|
end
|
138
134
|
end
|
@@ -369,29 +365,6 @@ module RunLoop
|
|
369
365
|
end.compact.sort
|
370
366
|
end
|
371
367
|
|
372
|
-
# @!visibility private
|
373
|
-
# The kill signal should be sent to instruments.
|
374
|
-
#
|
375
|
-
# When testing against iOS 8, sending -9 or 'TERM' causes the ScriptAgent
|
376
|
-
# process on the device to emit the following error until the device is
|
377
|
-
# rebooted.
|
378
|
-
#
|
379
|
-
# ```
|
380
|
-
# MobileGestaltHelper[909] <Error>: libMobileGestalt MobileGestalt.c:273: server_access_check denied access to question UniqueDeviceID for pid 796
|
381
|
-
# ScriptAgent[796] <Error>: libMobileGestalt MobileGestaltSupport.m:170: pid 796 (ScriptAgent) does not have sandbox access for re6Zb+zwFKJNlkQTUeT+/w and IS NOT appropriately entitled
|
382
|
-
# ScriptAgent[703] <Error>: libMobileGestalt MobileGestalt.c:534: no access to UniqueDeviceID (see <rdar://problem/11744455>)
|
383
|
-
# ```
|
384
|
-
#
|
385
|
-
# @see https://github.com/calabash/run_loop/issues/34
|
386
|
-
#
|
387
|
-
# @param [RunLoop::Xcode] xcode The Xcode tools to use to determine
|
388
|
-
# what version of Xcode is active.
|
389
|
-
# @return [String] Either 'QUIT' or 'TERM', depending on the Xcode
|
390
|
-
# version.
|
391
|
-
def kill_signal(xcode = RunLoop::Xcode.new)
|
392
|
-
xcode.version_gte_6? ? 'QUIT' : 'TERM'
|
393
|
-
end
|
394
|
-
|
395
368
|
# @!visibility private
|
396
369
|
#
|
397
370
|
# Execute an instruments command.
|
data/lib/run_loop/ipa.rb
CHANGED
@@ -25,7 +25,20 @@ module RunLoop
|
|
25
25
|
|
26
26
|
# @!visibility private
|
27
27
|
def to_s
|
28
|
-
|
28
|
+
cf_bundle_version = bundle_version
|
29
|
+
cf_bundle_short_version = short_bundle_version
|
30
|
+
|
31
|
+
if cf_bundle_version && cf_bundle_short_version
|
32
|
+
version = "#{cf_bundle_version.to_s}/#{cf_bundle_short_version}"
|
33
|
+
elsif cf_bundle_version
|
34
|
+
version = cf_bundle_version.to_s
|
35
|
+
elsif cf_bundle_short_version
|
36
|
+
version = cf_bundle_short_version
|
37
|
+
else
|
38
|
+
version = ""
|
39
|
+
end
|
40
|
+
|
41
|
+
"#<IPA #{bundle_identifier} #{version} #{path}>"
|
29
42
|
end
|
30
43
|
|
31
44
|
# @!visibility private
|
@@ -71,6 +84,22 @@ module RunLoop
|
|
71
84
|
app.distribution_signed?
|
72
85
|
end
|
73
86
|
|
87
|
+
# @!visibility private
|
88
|
+
def marketing_version
|
89
|
+
app.marketing_version
|
90
|
+
end
|
91
|
+
|
92
|
+
# See #marketing_version
|
93
|
+
alias_method :short_bundle_version, :marketing_version
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
def build_version
|
97
|
+
app.build_version
|
98
|
+
end
|
99
|
+
|
100
|
+
# See #build_version
|
101
|
+
alias_method :bundle_version, :build_version
|
102
|
+
|
74
103
|
private
|
75
104
|
|
76
105
|
# @!visibility private
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# @!visibility private
|
4
|
+
# An interface to the `simctl` command line tool for CoreSimulator.
|
5
|
+
#
|
6
|
+
# Replacement for SimControl.
|
7
|
+
class Simctl
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
DEFAULTS = {
|
11
|
+
:timeout => RunLoop::Environment.ci? ? 90 : 30,
|
12
|
+
:log_cmd => true
|
13
|
+
}
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
SIMCTL_PLIST_DIR = lambda {
|
17
|
+
dirname = File.dirname(__FILE__)
|
18
|
+
joined = File.join(dirname, '..', '..', 'plists', 'simctl')
|
19
|
+
File.expand_path(joined)
|
20
|
+
}.call
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def self.uia_automation_plist
|
24
|
+
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomation.plist')
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def self.uia_automation_plugin_plist
|
29
|
+
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomationPlugIn.plist')
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!visibility private
|
33
|
+
attr_accessor :device
|
34
|
+
|
35
|
+
# @!visibility private
|
36
|
+
#
|
37
|
+
# @param [RunLoop::Device] device Cannot be nil.
|
38
|
+
def initialize(device)
|
39
|
+
@device = device
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!visibility private
|
43
|
+
def to_s
|
44
|
+
"#<Simctl: #{device.name} #{device.udid}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
def inspect
|
49
|
+
to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!visibility private
|
53
|
+
#
|
54
|
+
# This method is not supported on Xcode < 7 - returns nil.
|
55
|
+
#
|
56
|
+
# @param [String] bundle_id The CFBundleIdentifier of the app.
|
57
|
+
# @return [String] The path to the .app bundle if it exists; nil otherwise.
|
58
|
+
def app_container(bundle_id)
|
59
|
+
return nil if !xcode.version_gte_7?
|
60
|
+
cmd = ["simctl", "get_app_container", device.udid, bundle_id]
|
61
|
+
hash = execute(cmd, DEFAULTS)
|
62
|
+
|
63
|
+
exit_status = hash[:exit_status]
|
64
|
+
if exit_status != 0
|
65
|
+
nil
|
66
|
+
else
|
67
|
+
hash[:out].strip
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# @!visibility private
|
74
|
+
def execute(array, options)
|
75
|
+
merged = DEFAULTS.merge(options)
|
76
|
+
xcrun.exec(array, merged)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @!visibility private
|
80
|
+
def xcrun
|
81
|
+
@xcrun ||= RunLoop::Xcrun.new
|
82
|
+
end
|
83
|
+
|
84
|
+
# @!visibility private
|
85
|
+
def xcode
|
86
|
+
@xcode ||= RunLoop::Xcode.new
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/run_loop/version.rb
CHANGED
data/lib/run_loop/xcuitest.rb
CHANGED
@@ -3,20 +3,44 @@ module RunLoop
|
|
3
3
|
# @!visibility private
|
4
4
|
class XCUITest
|
5
5
|
|
6
|
+
class HTTPError < RuntimeError; end
|
7
|
+
|
6
8
|
# @!visibility private
|
7
9
|
DEFAULTS = {
|
8
10
|
:port => 27753,
|
9
|
-
:simulator_ip => "127.0.0.1"
|
11
|
+
:simulator_ip => "127.0.0.1",
|
12
|
+
:http_timeout => RunLoop::Environment.ci? ? 120 : 60,
|
13
|
+
:version => "1.0"
|
10
14
|
}
|
11
15
|
|
12
16
|
# @!visibility private
|
13
|
-
def self.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
def self.run(options={})
|
18
|
+
# logger = options[:logger]
|
19
|
+
simctl = options[:sim_control] || options[:simctl] || RunLoop::SimControl.new
|
20
|
+
xcode = options[:xcode] || RunLoop::Xcode.new
|
21
|
+
instruments = options[:instruments] || RunLoop::Instruments.new
|
22
|
+
|
23
|
+
# Find the Device under test, the App under test, UIA strategy, and reset options
|
24
|
+
device = RunLoop::Device.detect_device(options, xcode, simctl, instruments)
|
25
|
+
app_details = RunLoop::DetectAUT.detect_app_under_test(options)
|
26
|
+
reset_options = RunLoop::Core.send(:detect_reset_options, options)
|
27
|
+
|
28
|
+
app = app_details[:app]
|
29
|
+
bundle_id = app_details[:bundle_id]
|
30
|
+
|
31
|
+
if device.simulator? && app
|
32
|
+
core_sim = RunLoop::CoreSimulator.new(device, app, :xcode => xcode)
|
33
|
+
if reset_options
|
34
|
+
core_sim.reset_app_sandbox
|
35
|
+
end
|
36
|
+
|
37
|
+
simctl.ensure_software_keyboard(device)
|
38
|
+
core_sim.install
|
19
39
|
end
|
40
|
+
|
41
|
+
xcuitest = RunLoop::XCUITest.new(bundle_id, device)
|
42
|
+
xcuitest.launch
|
43
|
+
xcuitest
|
20
44
|
end
|
21
45
|
|
22
46
|
# @!visibility private
|
@@ -30,78 +54,262 @@ module RunLoop
|
|
30
54
|
end
|
31
55
|
|
32
56
|
# @!visibility private
|
33
|
-
|
57
|
+
#
|
58
|
+
# The app with `bundle_id` needs to be installed.
|
59
|
+
#
|
60
|
+
# @param [String] bundle_id The identifier of the app under test.
|
61
|
+
# @param [RunLoop::Device] device The device device.
|
62
|
+
def initialize(bundle_id, device)
|
34
63
|
@bundle_id = bundle_id
|
64
|
+
@device = device
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"#<XCUITest #{url} : #{bundle_id} : #{device}>"
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
to_s
|
35
73
|
end
|
36
74
|
|
37
75
|
# @!visibility private
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
76
|
+
def bundle_id
|
77
|
+
@bundle_id
|
78
|
+
end
|
79
|
+
|
80
|
+
# @!visibility private
|
81
|
+
def device
|
82
|
+
@device
|
83
|
+
end
|
84
|
+
|
85
|
+
# @!visibility private
|
86
|
+
def workspace
|
87
|
+
@workspace ||= lambda do
|
88
|
+
path = RunLoop::Environment.send(:cbxws)
|
89
|
+
if path
|
90
|
+
path
|
48
91
|
else
|
49
|
-
|
50
|
-
encoding_options = {
|
51
|
-
:invalid => :replace, # Replace invalid byte sequences
|
52
|
-
:undef => :replace, # Replace anything not defined in ASCII
|
53
|
-
:replace => '' # Use a blank for those replacements
|
54
|
-
}
|
55
|
-
encoded = device_name.encode(Encoding.find("ASCII"), encoding_options)
|
56
|
-
"http://#{encoded}.local:27753"
|
92
|
+
raise "TODO: figure out how to distribute the CBX-Runner"
|
57
93
|
end
|
94
|
+
end.call
|
95
|
+
end
|
96
|
+
|
97
|
+
def launch
|
98
|
+
start = Time.now
|
99
|
+
launch_cbx_runner
|
100
|
+
launch_aut
|
101
|
+
elapsed = Time.now - start
|
102
|
+
RunLoop.log_debug("Took #{elapsed} seconds to launch #{bundle_id} on #{device}")
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
# @!visibility private
|
107
|
+
def running?
|
108
|
+
begin
|
109
|
+
health(ping_options)
|
110
|
+
rescue => _
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @!visibility private
|
116
|
+
def stop
|
117
|
+
begin
|
118
|
+
shutdown
|
119
|
+
rescue => _
|
120
|
+
nil
|
58
121
|
end
|
59
122
|
end
|
60
123
|
|
61
124
|
# @!visibility private
|
62
|
-
def
|
125
|
+
def launch_other_app(bundle_id)
|
126
|
+
launch_aut(bundle_id)
|
127
|
+
end
|
63
128
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
129
|
+
# @!visibility private
|
130
|
+
def query(mark)
|
131
|
+
options = http_options
|
132
|
+
parameters = { :text => mark }
|
133
|
+
request = request("query", parameters)
|
134
|
+
client = client(options)
|
135
|
+
response = client.post(request)
|
136
|
+
expect_200_response(response)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @!visibility private
|
140
|
+
def tap_mark(mark)
|
141
|
+
options = http_options
|
142
|
+
parameters = {
|
143
|
+
:gesture => "tap",
|
144
|
+
:text => mark
|
70
145
|
}
|
71
|
-
|
146
|
+
request = request("gesture", parameters)
|
147
|
+
client(options)
|
148
|
+
response = client.post(request)
|
149
|
+
expect_200_response(response)
|
150
|
+
end
|
72
151
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
152
|
+
# @!visibility private
|
153
|
+
def tap_coordinate(x, y)
|
154
|
+
options = http_options
|
155
|
+
parameters = {
|
156
|
+
:gesture => "tap_coordinate",
|
157
|
+
:coordinate => {x: x, y: y}
|
158
|
+
}
|
159
|
+
request = request("gesture", parameters)
|
160
|
+
client(options)
|
161
|
+
response = client.post(request)
|
162
|
+
expect_200_response(response)
|
163
|
+
end
|
80
164
|
|
81
|
-
|
165
|
+
# @!visibility private
|
166
|
+
def tap_query_result(hash)
|
167
|
+
rect = hash["rect"]
|
168
|
+
h = rect["height"]
|
169
|
+
w = rect["width"]
|
170
|
+
x = rect["x"]
|
171
|
+
y = rect["y"]
|
172
|
+
|
173
|
+
touchx = x + (h/2)
|
174
|
+
touchy = y + (w/2)
|
175
|
+
tap_coordinate(touchx, touchy)
|
176
|
+
end
|
82
177
|
|
83
|
-
|
84
|
-
raise RuntimeError, "No workspace found"
|
85
|
-
end
|
178
|
+
private
|
86
179
|
|
87
|
-
|
180
|
+
# @!visibility private
|
181
|
+
def xcrun
|
182
|
+
RunLoop::Xcrun.new
|
183
|
+
end
|
88
184
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
185
|
+
# @!visibility private
|
186
|
+
def url
|
187
|
+
@url ||= lambda do
|
188
|
+
if device.simulator?
|
189
|
+
"http://#{DEFAULTS[:simulator_ip]}:#{DEFAULTS[:port]}/"
|
190
|
+
else
|
191
|
+
# This block is untested.
|
192
|
+
calabash_endpoint = RunLoop::Environment.device_endpoint
|
193
|
+
if calabash_endpoint
|
194
|
+
base = calabash_endpoint.split(":")[0..1].join(":")
|
195
|
+
"http://#{base}:#{DEFAULTS[:port]}/"
|
196
|
+
else
|
197
|
+
device_name = device.name.gsub(/['\s]/, "")
|
198
|
+
encoding_options = {
|
199
|
+
:invalid => :replace, # Replace invalid byte sequences
|
200
|
+
:undef => :replace, # Replace anything not defined in ASCII
|
201
|
+
:replace => "" # Use a blank for those replacements
|
202
|
+
}
|
203
|
+
encoded = device_name.encode(Encoding.find("ASCII"), encoding_options)
|
204
|
+
"http://#{encoded}.local:27753/"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end.call
|
208
|
+
end
|
209
|
+
|
210
|
+
# @!visibility private
|
211
|
+
def server
|
212
|
+
@server ||= RunLoop::HTTP::Server.new(url)
|
213
|
+
end
|
214
|
+
|
215
|
+
# @!visibility private
|
216
|
+
def client(options={})
|
217
|
+
RunLoop::HTTP::RetriableClient.new(server, options)
|
218
|
+
end
|
219
|
+
|
220
|
+
# @!visibility private
|
221
|
+
def versioned_route(route)
|
222
|
+
if ["health", "ping", "sessionIdentifier"].include?(route)
|
223
|
+
route
|
94
224
|
else
|
225
|
+
"#{DEFAULTS[:version]}/#{route}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# @!visibility private
|
230
|
+
def request(route, parameters={})
|
231
|
+
versioned = versioned_route(route)
|
232
|
+
RunLoop::HTTP::Request.request(versioned, parameters)
|
233
|
+
end
|
234
|
+
|
235
|
+
# @!visibility private
|
236
|
+
def ping_options
|
237
|
+
@ping_options ||= { :timeout => 0.5, :retries => 1 }
|
238
|
+
end
|
95
239
|
|
240
|
+
# @!visibility private
|
241
|
+
def http_options
|
242
|
+
{
|
243
|
+
:timeout => DEFAULTS[:http_timeout],
|
244
|
+
:interval => 0.1,
|
245
|
+
:retries => (DEFAULTS[:http_timeout]/0.1).to_i
|
246
|
+
}
|
247
|
+
end
|
248
|
+
|
249
|
+
# @!visibility private
|
250
|
+
def session_delete
|
251
|
+
options = ping_options
|
252
|
+
request = request("delete")
|
253
|
+
client = client(options)
|
254
|
+
begin
|
255
|
+
response = client.delete(request)
|
256
|
+
body = expect_200_response(response)
|
257
|
+
RunLoop.log_debug("CBX-Runner says, #{body}")
|
258
|
+
body
|
259
|
+
rescue => e
|
260
|
+
RunLoop.log_debug("CBX-Runner session delete error: #{e}")
|
261
|
+
nil
|
96
262
|
end
|
263
|
+
end
|
97
264
|
|
265
|
+
# @!visibility private
|
266
|
+
# TODO expect 200 response and parse body (atm the body in not valid JSON)
|
267
|
+
def shutdown
|
268
|
+
session_delete
|
269
|
+
options = ping_options
|
270
|
+
request = request("shutdown")
|
271
|
+
client = client(options)
|
272
|
+
begin
|
273
|
+
response = client.post(request)
|
274
|
+
body = response.body
|
275
|
+
RunLoop.log_debug("CBX-Runner says, \"#{body}\"")
|
276
|
+
5.times do
|
277
|
+
begin
|
278
|
+
health
|
279
|
+
sleep(0.2)
|
280
|
+
rescue => _
|
281
|
+
break
|
282
|
+
end
|
283
|
+
end
|
284
|
+
body
|
285
|
+
rescue => e
|
286
|
+
RunLoop.log_debug("CBX-Runner shutdown error: #{e}")
|
287
|
+
nil
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# @!visibility private
|
292
|
+
# TODO expect 200 response and parse body (atm the body is not valid JSON)
|
293
|
+
def health(options={})
|
294
|
+
merged_options = http_options.merge(options)
|
295
|
+
request = request("health")
|
296
|
+
client = client(merged_options)
|
297
|
+
response = client.get(request)
|
298
|
+
body = response.body
|
299
|
+
RunLoop.log_debug("CBX-Runner driver says, \"#{body}\"")
|
300
|
+
body
|
301
|
+
end
|
302
|
+
|
303
|
+
# @!visibility private
|
304
|
+
def xcodebuild
|
98
305
|
args = [
|
99
306
|
"xcrun",
|
100
307
|
"xcodebuild",
|
101
308
|
"-scheme", "CBXAppStub",
|
102
309
|
"-workspace", workspace,
|
103
310
|
"-config", "Debug",
|
104
|
-
"-destination",
|
311
|
+
"-destination",
|
312
|
+
"id=#{device.udid}",
|
105
313
|
"clean",
|
106
314
|
"test"
|
107
315
|
]
|
@@ -118,82 +326,80 @@ module RunLoop
|
|
118
326
|
|
119
327
|
pid = Process.spawn(*args, options)
|
120
328
|
Process.detach(pid)
|
329
|
+
pid
|
330
|
+
end
|
121
331
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
332
|
+
# @!visibility private
|
333
|
+
def launch_cbx_runner
|
334
|
+
# Fail fast if CBXWS is not defined.
|
335
|
+
# WIP - we will distribute the workspace somehow.
|
336
|
+
workspace
|
127
337
|
|
128
|
-
|
129
|
-
request = RunLoop::HTTP::Request.new("/health", {})
|
338
|
+
shutdown
|
130
339
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
340
|
+
if device.simulator?
|
341
|
+
# quits the simulator
|
342
|
+
sim = CoreSimulator.new(device, "")
|
343
|
+
sim.launch_simulator
|
344
|
+
else
|
345
|
+
# anything special about physical devices?
|
346
|
+
end
|
136
347
|
|
137
|
-
|
138
|
-
|
348
|
+
start = Time.now
|
349
|
+
pid = xcodebuild
|
350
|
+
RunLoop.log_debug("Waiting for CBX-Runner to build...")
|
351
|
+
health
|
139
352
|
|
140
|
-
RunLoop.log_debug("
|
353
|
+
RunLoop.log_debug("Took #{Time.now - start} seconds to build and launch")
|
141
354
|
pid.to_i
|
142
355
|
end
|
143
356
|
|
144
|
-
def launch_app
|
145
|
-
server = RunLoop::HTTP::Server.new(url)
|
146
|
-
request = RunLoop::HTTP::Request.request("/session", {:bundleID => bundle_id})
|
147
|
-
client = RunLoop::HTTP::RetriableClient.new(server)
|
148
|
-
response = client.post(request)
|
149
|
-
|
150
|
-
RunLoop.log_debug("Calabus driver says, \"#{response.body}\"")
|
151
|
-
end
|
152
|
-
|
153
357
|
# @!visibility private
|
154
|
-
def
|
155
|
-
|
156
|
-
|
358
|
+
def launch_aut(bundle_id = @bundle_id)
|
359
|
+
client = client(http_options)
|
360
|
+
request = request("session", {:bundleID => bundle_id})
|
157
361
|
|
158
|
-
|
159
|
-
|
362
|
+
begin
|
363
|
+
response = client.post(request)
|
364
|
+
RunLoop.log_debug("Launched #{bundle_id} on #{device}")
|
365
|
+
RunLoop.log_debug("#{response.body}")
|
366
|
+
if device.simulator?
|
367
|
+
device.simulator_wait_for_stable_state
|
160
368
|
end
|
369
|
+
expect_200_response(response)
|
370
|
+
rescue => e
|
371
|
+
raise e.class, %Q[Could not launch #{bundle_id} on #{device}:
|
161
372
|
|
162
|
-
|
163
|
-
:sim_control => simctl,
|
164
|
-
:instruments => instruments
|
165
|
-
}
|
166
|
-
|
167
|
-
device = RunLoop::Device.device_with_identifier(target, options)
|
168
|
-
|
169
|
-
if !device
|
170
|
-
raise RuntimeError, "Could not find a device"
|
171
|
-
end
|
373
|
+
#{e.message}
|
172
374
|
|
173
|
-
|
174
|
-
|
375
|
+
Something went wrong.
|
376
|
+
]
|
377
|
+
end
|
175
378
|
end
|
176
379
|
|
177
380
|
# @!visibility private
|
178
|
-
def
|
179
|
-
|
381
|
+
def response_body_to_hash(response)
|
382
|
+
body = response.body
|
383
|
+
begin
|
384
|
+
JSON.parse(body)
|
385
|
+
rescue TypeError, JSON::ParserError => _
|
386
|
+
raise RunLoop::XCUITest::HTTPError,
|
387
|
+
"Could not parse response '#{body}'; the app has probably crashed"
|
388
|
+
end
|
180
389
|
end
|
181
390
|
|
182
|
-
private
|
183
|
-
|
184
391
|
# @!visibility private
|
185
|
-
def
|
186
|
-
|
187
|
-
|
392
|
+
def expect_200_response(response)
|
393
|
+
body = response_body_to_hash(response)
|
394
|
+
return body if response.status_code < 300
|
188
395
|
|
189
|
-
|
190
|
-
|
191
|
-
@instruments ||= RunLoop::Instruments.new
|
192
|
-
end
|
396
|
+
raise RunLoop::XCUITest::HTTPError,
|
397
|
+
%Q[Expected status code < 200, found #{response.status_code}.
|
193
398
|
|
194
|
-
|
195
|
-
|
196
|
-
|
399
|
+
Server replied with:
|
400
|
+
|
401
|
+
#{body}
|
402
|
+
]
|
197
403
|
end
|
198
404
|
|
199
405
|
# @!visibility private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: run_loop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karl Krukow
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -326,7 +326,7 @@ files:
|
|
326
326
|
- lib/run_loop/process_waiter.rb
|
327
327
|
- lib/run_loop/regex.rb
|
328
328
|
- lib/run_loop/sim_control.rb
|
329
|
-
- lib/run_loop/simctl
|
329
|
+
- lib/run_loop/simctl.rb
|
330
330
|
- lib/run_loop/strings.rb
|
331
331
|
- lib/run_loop/template.rb
|
332
332
|
- lib/run_loop/version.rb
|
@@ -362,9 +362,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
362
362
|
version: '2.0'
|
363
363
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
364
364
|
requirements:
|
365
|
-
- - "
|
365
|
+
- - ">="
|
366
366
|
- !ruby/object:Gem::Version
|
367
|
-
version:
|
367
|
+
version: '0'
|
368
368
|
requirements: []
|
369
369
|
rubyforge_project:
|
370
370
|
rubygems_version: 2.5.2
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module RunLoop
|
2
|
-
module Simctl
|
3
|
-
class Plists
|
4
|
-
|
5
|
-
SIMCTL_PLIST_DIR = lambda {
|
6
|
-
dirname = File.dirname(__FILE__)
|
7
|
-
joined = File.join(dirname, '..', '..', '..', 'plists', 'simctl')
|
8
|
-
File.expand_path(joined)
|
9
|
-
}.call
|
10
|
-
|
11
|
-
def self.uia_automation_plist
|
12
|
-
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomation.plist')
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.uia_automation_plugin_plist
|
16
|
-
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomationPlugIn.plist')
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|