run_loop 2.1.2 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -294,7 +294,11 @@ module RunLoop
294
294
  end
295
295
  else
296
296
  possible = XCODE_5_SDKS.map do |sdk|
297
- File.expand_path("~/Library/Application Support/iPhone Simulator/#{sdk}")
297
+ File.join(RunLoop::Environment.user_home_directory,
298
+ "Library",
299
+ "Application Support",
300
+ "iPhone Simulator",
301
+ sdk)
298
302
  end
299
303
 
300
304
  dirs = (possible + existing).uniq
@@ -646,10 +650,11 @@ module RunLoop
646
650
  # The absolute path to the iPhone Simulator Application Support directory.
647
651
  # @return [String] absolute path
648
652
  def sim_app_support_dir
653
+ home_dir = RunLoop::Environment.user_home_directory
649
654
  if xcode_version_gte_6?
650
- File.expand_path('~/Library/Developer/CoreSimulator/Devices')
655
+ File.join(home_dir, "Library", "Developer", "CoreSimulator", "Devices")
651
656
  else
652
- File.expand_path('~/Library/Application Support/iPhone Simulator')
657
+ File.join(home_dir, "Library", "Application Support", "iPhone Simulator")
653
658
  end
654
659
  end
655
660
 
@@ -1,5 +1,5 @@
1
1
  module RunLoop
2
- VERSION = "2.1.2"
2
+ VERSION = "2.1.3"
3
3
 
4
4
  # A model of a software release version that can be used to compare two versions.
5
5
  #
@@ -3,6 +3,9 @@ module RunLoop
3
3
  # @!visibility private
4
4
  class XCUITest
5
5
 
6
+ require "run_loop/shell"
7
+ include RunLoop::Shell
8
+
6
9
  class HTTPError < RuntimeError; end
7
10
 
8
11
  # @!visibility private
@@ -38,59 +41,64 @@ module RunLoop
38
41
  core_sim.install
39
42
  end
40
43
 
41
- xcuitest = RunLoop::XCUITest.new(bundle_id, device)
44
+ cbx_launcher = XCUITest.detect_cbx_launcher(options, device)
45
+
46
+ xcuitest = RunLoop::XCUITest.new(bundle_id, device, cbx_launcher)
42
47
  xcuitest.launch
43
48
  xcuitest
44
49
  end
45
50
 
46
51
  # @!visibility private
47
- def self.xcodebuild_log_file
48
- path = File.join(XCUITest.dot_dir, "xcodebuild.log")
49
- FileUtils.touch(path) if !File.exist?(path)
50
- path
52
+ #
53
+ # @param [RunLoop::Device] device the device under test
54
+ def self.default_cbx_launcher(device)
55
+ RunLoop::DeviceAgent::XCTestctl.new(device)
56
+ end
57
+
58
+ # @!visibility private
59
+ # @param [Hash] options the options passed by the user
60
+ # @param [RunLoop::Device] device the device under test
61
+ def self.detect_cbx_launcher(options, device)
62
+ value = options[:cbx_launcher]
63
+ if value
64
+ if value == :xcodebuild
65
+ RunLoop::DeviceAgent::Xcodebuild.new(device)
66
+ elsif value == :xctestctl
67
+ RunLoop::DeviceAgent::XCTestctl.new(device)
68
+ else
69
+ raise(ArgumentError,
70
+ "Expected :cbx_launcher => #{value} to be :xcodebuild or :xctestctl")
71
+ end
72
+ else
73
+ XCUITest.default_cbx_launcher(device)
74
+ end
51
75
  end
52
76
 
77
+ attr_reader :bundle_id, :device, :cbx_launcher
78
+
53
79
  # @!visibility private
54
80
  #
55
81
  # The app with `bundle_id` needs to be installed.
56
82
  #
57
83
  # @param [String] bundle_id The identifier of the app under test.
58
84
  # @param [RunLoop::Device] device The device device.
59
- def initialize(bundle_id, device)
85
+ def initialize(bundle_id, device, cbx_launcher)
60
86
  @bundle_id = bundle_id
61
87
  @device = device
88
+ @cbx_launcher = cbx_launcher
62
89
  end
63
90
 
91
+ # @!visibility private
64
92
  def to_s
65
- "#<XCUITest #{url} : #{bundle_id} : #{device}>"
93
+ "#<XCUITest #{url} : #{bundle_id} : #{device} : #{cbx_launcher}>"
66
94
  end
67
95
 
96
+ # @!visibility private
68
97
  def inspect
69
98
  to_s
70
99
  end
71
100
 
72
101
  # @!visibility private
73
- def bundle_id
74
- @bundle_id
75
- end
76
-
77
- # @!visibility private
78
- def device
79
- @device
80
- end
81
-
82
- # @!visibility private
83
- def workspace
84
- @workspace ||= lambda do
85
- path = RunLoop::Environment.send(:cbxws)
86
- if path
87
- path
88
- else
89
- raise "TODO: figure out how to distribute the CBX-Runner"
90
- end
91
- end.call
92
- end
93
-
94
102
  def launch
95
103
  start = Time.now
96
104
  launch_cbx_runner
@@ -123,6 +131,15 @@ module RunLoop
123
131
  launch_aut(bundle_id)
124
132
  end
125
133
 
134
+ # @!visibility private
135
+ def runtime
136
+ options = http_options
137
+ request = request("device")
138
+ client = client(options)
139
+ response = client.get(request)
140
+ expect_200_response(response)
141
+ end
142
+
126
143
  # @!visibility private
127
144
  def query(mark)
128
145
  options = http_options
@@ -134,23 +151,50 @@ module RunLoop
134
151
  end
135
152
 
136
153
  # @!visibility private
137
- def tap_mark(mark)
154
+ def query_for_coordinate(mark)
138
155
  body = query(mark)
139
- tap_query_result(body)
156
+ coordinate_from_query_result(body)
140
157
  end
141
158
 
142
159
  # @!visibility private
143
- def tap_coordinate(x, y)
144
- options = http_options
145
- parameters = {
146
- :gesture => "touch",
160
+ def tap_mark(mark)
161
+ coordinate = query_for_coordinate(mark)
162
+ tap_coordinate(coordinate[:x], coordinate[:y])
163
+ end
164
+
165
+ # @!visibility private
166
+ def tap_coordinate(x, y, options)
167
+ make_coordinate_gesture_request("touch", x, y)
168
+ end
169
+
170
+ # @!visibility private
171
+ def double_tap(x, y)
172
+ make_coordinate_gesture_request("double_tap", x, y)
173
+ end
174
+
175
+ # @!visibiity private
176
+ def coordinate_gesture_parameters(name, x, y, options={})
177
+ {
178
+ :gesture => name,
147
179
  :specifiers => {
148
- :coordinate => {x: x, y: y}
180
+ :coordinate => [x, y]
149
181
  },
150
- :options => {}
182
+ :options => options
151
183
  }
152
- request = request("gesture", parameters)
153
- client(options)
184
+ end
185
+
186
+ # @!visibility private
187
+ def coordinate_gesture_request(name, x, y, gesture_options={})
188
+ parameters = coordinate_gesture_parameters(name, x, y, gesture_options)
189
+ request("gesture", parameters)
190
+ end
191
+
192
+ # @!visibility private
193
+ def make_coordinate_gesture_request(name, x, y, options={})
194
+ gesture_options = options.fetch(:gesture_options, {})
195
+ http_options = options.fetch(:http_options, http_options())
196
+ request = coordinate_gesture_request(name, x, y, gesture_options)
197
+ client = client(http_options)
154
198
  response = client.post(request)
155
199
  expect_200_response(response)
156
200
  end
@@ -162,7 +206,7 @@ module RunLoop
162
206
  :orientation => orientation
163
207
  }
164
208
  request = request("rotate_home_button_to", parameters)
165
- client(http_options)
209
+ client = client(http_options)
166
210
  response = client.post(request)
167
211
  json = expect_200_response(response)
168
212
  sleep(sleep_for)
@@ -179,23 +223,46 @@ module RunLoop
179
223
  :options => options
180
224
  }
181
225
 
226
+ RunLoop.log_debug(%Q[
227
+ Sending request to perform '#{gesture}' with:
228
+
229
+ #{JSON.pretty_generate(parameters)}
230
+
231
+ ])
182
232
  request = request("gesture", parameters)
183
- client(http_options)
233
+ client = client(http_options)
184
234
  response = client.post(request)
185
235
  expect_200_response(response)
186
236
  end
187
237
 
188
238
  # @!visibility private
189
- def tap_query_result(hash)
190
- rect = hash["rect"]
239
+ def coordinate_from_query_result(hash)
240
+ matches = hash["result"]
241
+
242
+ if matches.nil? || matches.empty?
243
+ raise "Expected #{hash} to contain some results"
244
+ end
245
+
246
+ rect = matches.first["rect"]
191
247
  h = rect["height"]
192
248
  w = rect["width"]
193
249
  x = rect["x"]
194
250
  y = rect["y"]
195
251
 
196
- touchx = x + (h/2)
197
- touchy = y + (w/2)
198
- tap_coordinate(touchx, touchy)
252
+ touchx = x + (w/2.0)
253
+ touchy = y + (h/2.0)
254
+
255
+ new_rect = rect.dup
256
+ new_rect[:center_x] = touchx
257
+ new_rect[:center_y] = touchy
258
+
259
+ RunLoop.log_debug(%Q[Rect from query:
260
+
261
+ #{JSON.pretty_generate(new_rect)}
262
+
263
+ ])
264
+ {:x => touchx,
265
+ :y => touchy}
199
266
  end
200
267
 
201
268
  private
@@ -223,7 +290,7 @@ module RunLoop
223
290
  :undef => :replace, # Replace anything not defined in ASCII
224
291
  :replace => "" # Use a blank for those replacements
225
292
  }
226
- encoded = device_name.encode(Encoding.find("ASCII"), encoding_options)
293
+ encoded = device_name.encode(::Encoding.find("ASCII"), encoding_options)
227
294
  "http://#{encoded}.local:27753/"
228
295
  end
229
296
  end
@@ -296,14 +363,6 @@ module RunLoop
296
363
  response = client.post(request)
297
364
  body = response.body
298
365
  RunLoop.log_debug("CBX-Runner says, \"#{body}\"")
299
- 5.times do
300
- begin
301
- health
302
- sleep(1.0)
303
- rescue => _
304
- break
305
- end
306
- end
307
366
  body
308
367
  rescue => e
309
368
  RunLoop.log_debug("CBX-Runner shutdown error: #{e}")
@@ -323,65 +382,22 @@ module RunLoop
323
382
  body
324
383
  end
325
384
 
326
- # @!visibility private
327
- def xcodebuild
328
- env = {
329
- "COMMAND_LINE_BUILD" => "1"
330
- }
331
-
332
- args = [
333
- "xcrun",
334
- "xcodebuild",
335
- "-scheme", "CBXAppStub",
336
- "-workspace", workspace,
337
- "-config", "Debug",
338
- "-destination",
339
- "id=#{device.udid}",
340
- "clean",
341
- "test"
342
- ]
343
-
344
- log_file = XCUITest.xcodebuild_log_file
345
-
346
- options = {
347
- :out => log_file,
348
- :err => log_file
349
- }
350
-
351
- command = "#{env.map.each { |k, v| "#{k}=#{v}" }.join(" ")} #{args.join(" ")}"
352
- RunLoop.log_unix_cmd("#{command} >& #{log_file}")
353
-
354
- pid = Process.spawn(env, *args, options)
355
- Process.detach(pid)
356
- pid
357
- end
358
-
359
385
  # @!visibility private
360
386
  def launch_cbx_runner
361
- # Fail fast if CBXWS is not defined.
362
- # WIP - we will distribute the workspace somehow.
363
- workspace
364
-
365
387
  shutdown
366
388
 
367
- # Temp measure; we need to manage the xcodebuild pids.
368
- system("pkill xcodebuild")
369
-
370
- if device.simulator?
371
- # quits the simulator
372
- sim = CoreSimulator.new(device, "")
373
- sim.launch_simulator
374
- else
375
- # anything special about physical devices?
376
- end
389
+ options = {:log_cmd => true}
390
+ exec(["pkill", "xctestctl"], options)
391
+ exec(["pkill", "testmanagerd"], options)
392
+ exec(["pkill", "xcodebuild"], options)
377
393
 
378
394
  start = Time.now
379
- pid = xcodebuild
380
- RunLoop.log_debug("Waiting for CBX-Runner to build...")
395
+ RunLoop.log_debug("Waiting for CBX-Runner to launch...")
396
+ pid = cbx_launcher.launch
381
397
  health
398
+ RunLoop.log_debug("Took #{Time.now - start} launch and respond to /health")
382
399
 
383
- RunLoop.log_debug("Took #{Time.now - start} seconds to build and launch")
384
- pid.to_i
400
+ pid
385
401
  end
386
402
 
387
403
  # @!visibility private
@@ -423,26 +439,28 @@ Something went wrong.
423
439
  # @!visibility private
424
440
  def expect_200_response(response)
425
441
  body = response_body_to_hash(response)
426
- return body if response.status_code < 300
442
+ if response.status_code < 300 && !body["error"]
443
+ return body
444
+ end
427
445
 
428
- raise RunLoop::XCUITest::HTTPError,
429
- %Q[Expected status code < 200, found #{response.status_code}.
446
+ if response.status_code > 300
447
+ raise RunLoop::XCUITest::HTTPError,
448
+ %Q[Expected status code < 300, found #{response.status_code}.
430
449
 
431
450
  Server replied with:
432
451
 
433
452
  #{body}
453
+
434
454
  ]
435
- end
455
+ else
456
+ raise RunLoop::XCUITest::HTTPError,
457
+ %Q[Expected JSON response with no error, but found
436
458
 
437
- # @!visibility private
438
- def self.dot_dir
439
- path = File.join(RunLoop::DotDir.directory, "xcuitest")
459
+ #{body["error"]}
440
460
 
441
- if !File.exist?(path)
442
- FileUtils.mkdir_p(path)
443
- end
461
+ ]
444
462
 
445
- path
463
+ end
446
464
  end
447
465
 
448
466
  # @!visibility private
@@ -17,7 +17,7 @@ var Log = (function () {
17
17
 
18
18
  return {
19
19
  result: function (status, data) {
20
- log_json({"status": status, "value": data, "index":_actualIndex})
20
+ log_json({"status": status, "value": data, "index":_actualIndex});
21
21
  },
22
22
  output: function (msg) {
23
23
  log_json({"output": msg,"last_index":_actualIndex});
@@ -5,9 +5,9 @@ function findAlertTitle(alert) {
5
5
  var title = alert.name();
6
6
  var staticTexts;
7
7
 
8
- if (title == null) {
8
+ if (title === null) {
9
9
  staticTexts = alert.staticTexts();
10
- if (staticTexts != null && staticTexts.length > 0) {
10
+ if (staticTexts !== null && staticTexts.length > 0) {
11
11
 
12
12
  title = staticText[0].name();
13
13
  }
@@ -49,31 +49,85 @@ function englishLocalizations() {
49
49
 
50
50
  function danishLocalizations() {
51
51
  return [
52
- // Location
53
52
  ["Tillad", /bruge din lokalitet, når du bruger appen/],
54
53
  ["Tillad", /også når du ikke bruger appen/],
55
- ["OK", /vil bruge din aktuelle placering/]
54
+ ["OK", /vil bruge din aktuelle placering/],
55
+ ["OK", /vil bruge dine kontakter/],
56
+ ["OK", /vil bruge mikrofonen/],
57
+ ["OK", /vil bruge din kalender/],
58
+ ["OK", /vil bruge dine påmindelser/],
59
+ ["OK", /vil bruge dine fotos/],
60
+ ["OK", /ønsker adgang til Twitter-konti/],
61
+ ["OK", /vil bruge din fysiske aktivitet og din træningsaktivitet/],
62
+ ["OK", /vil bruge kameraet/],
63
+ ["OK", /vil gerne sende dig meddelelser/]
56
64
  ];
57
65
  }
58
66
 
59
- function spanishLocalizations() {
67
+ function euSpanishLocalizations() {
68
+ return [
69
+ ["Permitir", /acceder a tu ubicación mientras utilizas la aplicación/],
70
+ ["Permitir", /acceder a tu ubicación aunque no estés utilizando la aplicación/],
71
+ ["OK", /quiere acceder a tus contactos/],
72
+ ["OK", /quiere acceder a tu calendario/],
73
+ ["OK", /quiere acceder a tus recordatorios/],
74
+ ["OK", /quiere acceder a tus fotos/],
75
+ ["OK", /quiere obtener acceso a cuentas Twitter/],
76
+ ["OK", /quiere acceder al micrófono/],
77
+ ["OK", /desea acceder a tu actividad física y deportiva/],
78
+ ["OK", /quiere acceder a la cámara/],
79
+ ["OK", /quiere enviarte notificaciones/]
80
+ ];
81
+ }
82
+
83
+ function es419SpanishLocalizations() {
60
84
  return [
61
- // APNS
62
- ["OK", /enviarle notificaiones/]
85
+ ["Permitir", /acceda a tu ubicación mientras la app está en uso/],
86
+ ["Permitir", /acceda a tu ubicación incluso cuando la app no está en uso/],
87
+ ["OK", /quiere acceder a tu condición y actividad física/]
63
88
  ];
64
89
  }
65
90
 
91
+ function spanishLocalizations() {
92
+ return [].concat(
93
+ euSpanishLocalizations(),
94
+ es419SpanishLocalizations()
95
+ );
96
+ }
97
+
66
98
  function germanLocalizations() {
67
99
  return [
68
- // Location
69
- ["Ja", /Darf (?:.)+ Ihren aktuellen Ort verwenden/]
100
+ ["Ja", /Darf (?:.)+ Ihren aktuellen Ort verwenden/],
101
+ ["Erlauben", /auf Ihren Standort zugreifen, wenn Sie die App benutzen/],
102
+ ["Erlauben", /auch auf Ihren Standort zugreifen, wenn Sie die App nicht benutzen/],
103
+ ["Erlauben", /auf Ihren Standort zugreifen, selbst wenn Sie die App nicht benutzen/],
104
+ ["Ja", /auf Ihre Kontakte zugreifen/],
105
+ ["Ja", /auf Ihren Kalender zugreifen/],
106
+ ["Ja", /auf Ihre Erinnerungen zugreifen/],
107
+ ["Ja", /auf Ihre Fotos zugreifen/],
108
+ ["Erlauben", /möchte auf Twitter-Accounts zugreifen/],
109
+ ["Ja", /auf das Mikrofon zugreifen/],
110
+ ["Ja", /möchte auf Ihre Bewegungs- und Fitnessdaten zugreifen/],
111
+ ["Ja", /auf Ihre Kamera zugreifen/],
112
+ ["OK", /Ihnen Mitteilungen senden/]
70
113
  ];
71
114
  }
72
115
 
73
116
  function dutchLocalizations() {
74
117
  return [
75
- // APNS
76
- ["OK", /wil u berichten stuern/]
118
+ ["Sta toe", /tot uw locatie toestaan terwijl u de app gebruikt/],
119
+ ["Sta toe", /toegang tot uw locatie toestaan terwijl u de app gebruikt/],
120
+ ["Sta toe", /ook toegang tot uw locatie toestaan, zelfs als u de app niet gebruikt/],
121
+ ["Sta toe", /toegang tot uw locatie toestaan, zelfs als u de app niet gebruikt/],
122
+ ["OK", /wil toegang tot uw contacten/],
123
+ ["OK", /wil toegang tot uw agenda/],
124
+ ["OK", /wil toegang tot uw herinneringen/],
125
+ ["OK", /wil toegang tot uw foto's/],
126
+ ["OK", /wil toegang tot Twitter-accounts/],
127
+ ["OK", /wil toegang tot de microfoon/],
128
+ ["OK", /wil toegang tot uw bewegings- en fitnessactiviteit/],
129
+ ["OK", /wil toegang tot de camera/],
130
+ ["OK", /wil u berichten sturen/]
77
131
  ];
78
132
  }
79
133
 
@@ -89,13 +143,15 @@ function frenchLocalizations() {
89
143
  ["OK", /vous envoyer des notifications/],
90
144
  ["Autoriser", /à accéder à vos données de localisation lorsque vous utilisez l’app/],
91
145
  ["Autoriser", /à accéder à vos données de localisation même lorsque vous n’utilisez pas l’app/],
146
+ ["Autoriser", /à accéder aussi à vos données de localisation lorsque vous n’utilisez pas l’app/],
92
147
  ["OK", /souhaite accéder à vos contacts/],
93
148
  ["OK", /souhaite accéder à votre calendrier/],
94
149
  ["OK", /souhaite accéder à vos rappels/],
95
150
  ["OK", /souhaite accéder à vos mouvements et vos activités physiques/],
96
151
  ["OK", /souhaite accéder à vos photos/],
97
152
  ["OK", /souhaite accéder à l’appareil photo/],
98
- ["OK", /souhaite accéder aux comptes Twitter/]
153
+ ["OK", /souhaite accéder aux comptes Twitter/],
154
+ ["OK", /souhaite accéder au micro/]
99
155
  ];
100
156
  }
101
157
 
@@ -117,13 +173,42 @@ function isPrivacyAlert(alert) {
117
173
 
118
174
  var title = findAlertTitle(alert);
119
175
 
120
- // Comment this out if you are capturing regexes. See comment below.
121
- Log.output({"output":"alert: " + title}, true);
176
+ // Comment this out when capturing new regexes. See the comment below.
177
+ Log.output({"alert":title});
122
178
 
123
179
  // When debugging or trying to capture the regexes for a new
124
- // localization, uncomment these lines and comment out the line above.
125
- // var buttonNames = findAlertButtonNames(alert);
126
- // Log.output({"output":"alert: " + title + "," + buttonNames}, true);
180
+ // localization, uncomment the logging below.
181
+ //
182
+ // Notes:
183
+ // * Microphone alerts only appear on physical devices.
184
+ // * You have to click through the Health alert; it is completely blocking -
185
+ // tests will not proceed.
186
+ // * Generating bluetooth alerts is NYI.
187
+ // * In general, there will be a different alert on device for using location
188
+ // in background mode.
189
+ // * Alerts vary by iOS.
190
+ // * To reset notifications on devices:
191
+ // General > Settings > Reset > Reset Location + Privacy
192
+ // - These are the last table rows
193
+ // - APNS permissions are reset once a day.
194
+ // * On devices, set the device language in Settings.
195
+ //
196
+ // Use the Permission.app.
197
+ //
198
+ // * Alert text is printed at the end of every test.
199
+ // * Alert text is written to a file in Permissions/tmp.
200
+ //
201
+ // Examples:
202
+ //
203
+ // # Simulator running Mexican Spanish
204
+ // $ APP_LANG="es-MX" APP_LOCALE="es_MX" be cucumber -t @supported
205
+ //
206
+ // # Device running Dutch
207
+ // $ APP_LANG="nl" APP_LOCALE="nl" be cucumber -t @supported -p macmini
208
+
209
+ // This is very slow, so only do this if you are trying to capture regexes.
210
+ //var buttonNames = findAlertButtonNames(alert);
211
+ //Log.output({"alert":{"title":title, "buttons":buttonNames, "capture":"YES"}});
127
212
 
128
213
  var answer;
129
214
  var expression;