deliver 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +37 -10
  3. data/.rspec +1 -0
  4. data/.travis.yml +8 -0
  5. data/.yardopts +1 -0
  6. data/Gemfile.lock +106 -0
  7. data/LICENSE +21 -0
  8. data/README.md +268 -17
  9. data/Rakefile +3 -0
  10. data/assets/PDFExample.png +0 -0
  11. data/assets/SubmitForReviewInformation.png +0 -0
  12. data/assets/deliver.png +0 -0
  13. data/assets/deliver.pxm +0 -0
  14. data/assets/deliverFullSize.png +0 -0
  15. data/bin/deliver +32 -0
  16. data/deliver.gemspec +15 -9
  17. data/lib/assets/DeliverfileDefault +47 -0
  18. data/lib/assets/DeliverfileExample +62 -0
  19. data/lib/assets/ScreenshotsHelp +4 -0
  20. data/lib/deliver.rb +20 -1
  21. data/lib/deliver/app.rb +146 -0
  22. data/lib/deliver/app_metadata.rb +487 -0
  23. data/lib/deliver/app_screenshot.rb +119 -0
  24. data/lib/deliver/commands.rb +4 -0
  25. data/lib/deliver/commands/init.rb +12 -0
  26. data/lib/deliver/commands/run.rb +20 -0
  27. data/lib/deliver/deliver_process.rb +241 -0
  28. data/lib/deliver/deliverer.rb +112 -0
  29. data/lib/deliver/deliverfile/deliverfile.rb +35 -0
  30. data/lib/deliver/deliverfile/deliverfile_creator.rb +115 -0
  31. data/lib/deliver/deliverfile/dsl.rb +124 -0
  32. data/lib/deliver/dependency_checker.rb +32 -0
  33. data/lib/deliver/helper.rb +55 -0
  34. data/lib/deliver/ipa_uploader.rb +160 -0
  35. data/lib/deliver/itunes_connect.rb +514 -0
  36. data/lib/deliver/itunes_search_api.rb +48 -0
  37. data/lib/deliver/itunes_transporter.rb +154 -0
  38. data/lib/deliver/languages.rb +6 -0
  39. data/lib/deliver/metadata_item.rb +94 -0
  40. data/lib/deliver/password_manager.rb +86 -0
  41. data/lib/deliver/pdf_generator.rb +131 -0
  42. data/lib/deliver/version.rb +1 -1
  43. data/spec/app_metadata_spec.rb +350 -0
  44. data/spec/app_screenshot_spec.rb +88 -0
  45. data/spec/app_spec.rb +85 -0
  46. data/spec/deliverer_spec.rb +48 -0
  47. data/spec/deliverfile_creator_spec.rb +73 -0
  48. data/spec/example_deliver_files_spec.rb +227 -0
  49. data/spec/fixtures/Deliverfiles/DeliverfileCallbacks +14 -0
  50. data/spec/fixtures/Deliverfiles/DeliverfileCallbacksFailingTests +14 -0
  51. data/spec/fixtures/Deliverfiles/DeliverfileCallbacksNoErrorBlock +6 -0
  52. data/spec/fixtures/Deliverfiles/DeliverfileDefaultLanguageNotOnTop +8 -0
  53. data/spec/fixtures/Deliverfiles/DeliverfileDuplicateIpa +2 -0
  54. data/spec/fixtures/Deliverfiles/DeliverfileLocales +16 -0
  55. data/spec/fixtures/Deliverfiles/DeliverfileMetadataJson +6 -0
  56. data/spec/fixtures/Deliverfiles/DeliverfileMissingAppVersion +1 -0
  57. data/spec/fixtures/Deliverfiles/DeliverfileMissingBlockForTests +7 -0
  58. data/spec/fixtures/Deliverfiles/DeliverfileMissingIdentifier +1 -0
  59. data/spec/fixtures/Deliverfiles/DeliverfileMissingLanguage +1 -0
  60. data/spec/fixtures/Deliverfiles/DeliverfileMissingValue +3 -0
  61. data/spec/fixtures/Deliverfiles/DeliverfileMixed +37 -0
  62. data/spec/fixtures/Deliverfiles/DeliverfileNoVersion +3 -0
  63. data/spec/fixtures/Deliverfiles/DeliverfileScreenshots +6 -0
  64. data/spec/fixtures/Deliverfiles/DeliverfileScreenshotsFallbackDefaultLanguage +7 -0
  65. data/spec/fixtures/Deliverfiles/DeliverfileSimple +8 -0
  66. data/spec/fixtures/Deliverfiles/DeliverfileVersionMismatchPackage +8 -0
  67. data/spec/fixtures/Deliverfiles/DeliverfileWrongIdentifier +5 -0
  68. data/spec/fixtures/Deliverfiles/DeliverfileWrongVersion +5 -0
  69. data/spec/fixtures/Deliverfiles/metadata.json +24 -0
  70. data/spec/fixtures/example1.itmsp/metadata.xml +121 -0
  71. data/spec/fixtures/example2.itmsp/metadata.xml +54 -0
  72. data/spec/fixtures/ipas/Example1.ipa +0 -0
  73. data/spec/fixtures/metadata/ipa_result.xml +12 -0
  74. data/spec/fixtures/metadata/ipa_result2.xml +12 -0
  75. data/spec/fixtures/packages/464686641.itmsp/metadata.xml +104 -0
  76. data/spec/fixtures/packages/794902327.itmsp/metadata.xml +107 -0
  77. data/spec/fixtures/packages/878567776.itmsp/metadata.xml +104 -0
  78. data/spec/fixtures/screenshots/de-DE/iPhone4.png +0 -0
  79. data/spec/fixtures/screenshots/de-DE/iPhone6.png +0 -0
  80. data/spec/fixtures/screenshots/de-DE/iPhone6Plus1.png +0 -0
  81. data/spec/fixtures/screenshots/de-DE/iPhone6Plus2.png +0 -0
  82. data/spec/fixtures/screenshots/de-DE/screenshot1.png +0 -0
  83. data/spec/fixtures/screenshots/de-DE/screenshot2.png +0 -0
  84. data/spec/fixtures/screenshots/de-DE/screenshot3.png +0 -0
  85. data/spec/fixtures/screenshots/de-DE/screenshot5.png +0 -0
  86. data/spec/fixtures/screenshots/en-US/english.png +0 -0
  87. data/spec/fixtures/screenshots/iPhone4.png +0 -0
  88. data/spec/fixtures/screenshots/invalidImage.png +0 -0
  89. data/spec/fixtures/screenshots/screenshot1.png +0 -0
  90. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4 2.png +0 -0
  91. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4 copy 2.png +0 -0
  92. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4 copy 3.png +0 -0
  93. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4 copy 4.png +0 -0
  94. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4 copy 5.png +0 -0
  95. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4 copy 6.png +0 -0
  96. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4 copy.png +0 -0
  97. data/spec/fixtures/screenshots/tooMany/de-DE/iPhone4.png +0 -0
  98. data/spec/helper_spec.rb +16 -0
  99. data/spec/ipa_uploader_spec.rb +61 -0
  100. data/spec/itunes_connect_spec.rb +12 -0
  101. data/spec/itunes_search_api_spec.rb +24 -0
  102. data/spec/itunes_transporter_spec.rb +52 -0
  103. data/spec/languages_spec.rb +7 -0
  104. data/spec/metadata_item_spec.rb +36 -0
  105. data/spec/mocking/transporter_mocking.rb +40 -0
  106. data/spec/mocking/webmocking.rb +31 -0
  107. data/spec/password_manager_spec.rb +27 -0
  108. data/spec/responses/itunesLookup-.json +4 -0
  109. data/spec/responses/itunesLookup-0.json +4 -0
  110. data/spec/responses/itunesLookup-284882215.json +106 -0
  111. data/spec/responses/itunesLookup-at.felixkrause.iTanky.json +8 -0
  112. data/spec/responses/itunesLookup-com.facebook.Facebook.json +106 -0
  113. data/spec/responses/itunesLookup-invalid.json +4 -0
  114. data/spec/responses/itunesLookup-net.sunapps.invalid.json +4 -0
  115. data/spec/responses/transporter/download_invalid_apple_id.txt +35 -0
  116. data/spec/responses/transporter/download_valid_apple_id.txt +32 -0
  117. data/spec/responses/transporter/upload_invalid.txt +174 -0
  118. data/spec/responses/transporter/upload_valid.txt +290 -0
  119. data/spec/spec_helper.rb +23 -0
  120. data/tasks/rspec.rake +3 -0
  121. metadata +242 -8
  122. data/LICENSE.txt +0 -22
@@ -0,0 +1,514 @@
1
+ require 'deliver/password_manager'
2
+
3
+ require 'capybara'
4
+ require 'capybara/poltergeist'
5
+ require 'security'
6
+ require 'fastimage'
7
+
8
+
9
+ module Deliver
10
+ # Everything that can't be achived using the {Deliver::ItunesTransporter}
11
+ # will be scripted using the iTunesConnect frontend.
12
+ #
13
+ # Every method you call here, might take a time
14
+ class ItunesConnect
15
+ # This error occurs only if there is something wrong with the given login data
16
+ class ItunesConnectLoginError < StandardError
17
+ end
18
+
19
+ # This error can occur for many reaons. It is
20
+ # usually raised when a UI element could not be found
21
+ class ItunesConnectGeneralError < StandardError
22
+ end
23
+
24
+ include Capybara::DSL
25
+
26
+ ITUNESCONNECT_URL = "https://itunesconnect.apple.com/"
27
+ APP_DETAILS_URL = "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/[[app_id]]"
28
+
29
+ BUTTON_STRING_NEW_VERSION = "New Version"
30
+ BUTTON_STRING_SUBMIT_FOR_REVIEW = "Submit for Review"
31
+ BUTTON_ADD_NEW_BUILD = 'Click + to add a build before you submit your app.'
32
+
33
+ WAITING_FOR_REVIEW = "Waiting For Review"
34
+ PROCESSING_TEXT = "Processing"
35
+
36
+ def initialize
37
+ super
38
+
39
+ DependencyChecker.check_dependencies
40
+
41
+ Capybara.run_server = false
42
+ Capybara.default_driver = :poltergeist
43
+ Capybara.javascript_driver = :poltergeist
44
+ Capybara.current_driver = :poltergeist
45
+ Capybara.app_host = ITUNESCONNECT_URL
46
+
47
+ # Since Apple has some SSL errors, we have to configure the client properly:
48
+ # https://github.com/ariya/phantomjs/issues/11239
49
+ Capybara.register_driver :poltergeist do |a|
50
+ conf = ['--debug=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1']
51
+ Capybara::Poltergeist::Driver.new(a, {
52
+ phantomjs_options: conf,
53
+ phantomjs_logger: File.open("/tmp/poltergeist_log.txt", "a"),
54
+ })
55
+ end
56
+
57
+ self.login
58
+ end
59
+
60
+ # Loggs in a user with the given login data on the iTC Frontend.
61
+ # You don't need to pass a username and password. It will
62
+ # Automatically be fetched using the {Deliver::PasswordManager}.
63
+ # This method will also automatically be called when triggering other
64
+ # actions like {#open_app_page}
65
+ # @param user (String) (optional) The username/email address
66
+ # @param password (String) (optional) The password
67
+ # @return (bool) true if everything worked fine
68
+ # @raise [ItunesConnectGeneralError] General error while executing
69
+ # this action
70
+ # @raise [ItunesConnectLoginError] Login data is wrong
71
+ def login(user = nil, password = nil)
72
+ begin
73
+ Helper.log.info "Logging into iTunesConnect"
74
+
75
+ user ||= PasswordManager.new.username
76
+ password ||= PasswordManager.new.password
77
+
78
+ result = visit ITUNESCONNECT_URL
79
+ raise "Could not open iTunesConnect" unless result['status'] == 'success'
80
+
81
+ if page.has_content?"My Apps"
82
+ # Already logged in
83
+ return true
84
+ end
85
+
86
+ fill_in "accountname", with: user
87
+ fill_in "accountpassword", with: password
88
+
89
+ begin
90
+ wait_for_elements(".enabled").first.click
91
+ wait_for_elements('.ng-scope.managedWidth')
92
+ rescue
93
+ ItunesConnectLoginError.new("Error logging in user #{user} with the given password. Make sure you set them correctly")
94
+ end
95
+
96
+ Helper.log.info "Successfully logged into iTunesConnect"
97
+
98
+ true
99
+ rescue Exception => ex
100
+ error_occured(ex)
101
+ end
102
+ end
103
+
104
+ # Opens the app details page of the given app.
105
+ # @param app (Deliver::App) the app that should be opened
106
+ # @return (bool) true if everything worked fine
107
+ # @raise [ItunesConnectGeneralError] General error while executing
108
+ # this action
109
+ # @raise [ItunesConnectLoginError] Login data is wrong
110
+ def open_app_page(app)
111
+ begin
112
+ verify_app(app)
113
+
114
+ Helper.log.info "Opening detail page for app #{app}"
115
+
116
+ visit APP_DETAILS_URL.gsub("[[app_id]]", app.apple_id.to_s)
117
+
118
+ wait_for_elements('.page-subnav')
119
+ sleep 3
120
+
121
+ if current_url.include?"wa/defaultError" # app could not be found
122
+ raise "Could not open app details for app '#{app}'. Make sure you're using the correct Apple ID and the correct Apple developer account (#{PasswordManager.new.username}).".red
123
+ end
124
+
125
+ true
126
+ rescue Exception => ex
127
+ error_occured(ex)
128
+ end
129
+ end
130
+
131
+ # This method will fetch the current status ({Deliver::App::AppStatus})
132
+ # of your app and return it. This method uses a headless browser
133
+ # under the hood, so it might take some time until you get the result
134
+ # @param app (Deliver::App) the app you want this information from
135
+ # @raise [ItunesConnectGeneralError] General error while executing
136
+ # this action
137
+ # @raise [ItunesConnectLoginError] Login data is wrong
138
+ def get_app_status(app)
139
+ begin
140
+ verify_app(app)
141
+
142
+ open_app_page(app)
143
+
144
+ if page.has_content?WAITING_FOR_REVIEW
145
+ # That's either Upload Received or Waiting for Review
146
+ if page.has_content?"To submit a new build, you must remove this version from review"
147
+ return App::AppStatus::WAITING_FOR_REVIEW
148
+ else
149
+ return App::AppStatus::UPLOAD_RECEIVED
150
+ end
151
+ elsif page.has_content?BUTTON_STRING_NEW_VERSION
152
+ return App::AppStatus::READY_FOR_SALE
153
+ elsif page.has_content?BUTTON_STRING_SUBMIT_FOR_REVIEW
154
+ return App::AppStatus::PREPARE_FOR_SUBMISSION
155
+ else
156
+ raise "App status not yet implemented"
157
+ end
158
+ rescue Exception => ex
159
+ error_occured(ex)
160
+ end
161
+ end
162
+
163
+ # This method will fetch the version number of the currently live version
164
+ # of your app and return it. This method uses a headless browser
165
+ # under the hood, so it might take some time until you get the result
166
+ # @param app (Deliver::App) the app you want this information from
167
+ # @raise [ItunesConnectGeneralError] General error while executing
168
+ # this action
169
+ # @raise [ItunesConnectLoginError] Login data is wrong
170
+ def get_live_version(app)
171
+ begin
172
+ verify_app(app)
173
+
174
+ open_app_page(app)
175
+
176
+ return first(".status.ready").text.split(" ").first
177
+ rescue Exception => ex
178
+ error_occured(ex)
179
+ end
180
+ end
181
+
182
+
183
+
184
+ #####################################################
185
+ # @!group Constructive/Destructive Methods
186
+ #####################################################
187
+
188
+ # This method creates a new version of your app using the
189
+ # iTunesConnect frontend. This will happen directly after calling
190
+ # this method.
191
+ # @param app (Deliver::App) the app you want to modify
192
+ # @param version_number (String) the version number as string for
193
+ # the new version that should be created
194
+ def create_new_version!(app, version_number)
195
+ begin
196
+ current_version = get_live_version(app)
197
+
198
+ verify_app(app)
199
+ open_app_page(app)
200
+
201
+ if page.has_content?BUTTON_STRING_NEW_VERSION
202
+
203
+ if current_version == version_number
204
+ # This means, this version is already live on the App Store
205
+ raise "Version #{version_number} is already created, submitted and released on iTC. Please verify you're using a new version number."
206
+ end
207
+
208
+ click_on BUTTON_STRING_NEW_VERSION
209
+
210
+ Helper.log.info "Creating a new version (#{version_number})"
211
+
212
+ all(".fullWidth.nobottom.ng-isolate-scope.ng-pristine").last.set(version_number.to_s)
213
+ click_on "Create"
214
+
215
+ while not page.has_content?"Prepare for Submission"
216
+ sleep 1
217
+ Helper.log.debug("Waiting for 'Prepare for Submission'")
218
+ end
219
+ else
220
+ Helper.log.warn "Can not create version #{version_number} on iTunesConnect. Maybe it was already created."
221
+ Helper.log.info "Check out '#{current_url}' what's the latest version."
222
+
223
+ begin
224
+ created_version = first(".status.waiting").text.split(" ").first
225
+ if created_version != version_number
226
+ raise "Some other version ('#{created_version}') was created instead of the one you defined ('#{version_number}')"
227
+ end
228
+ rescue Exception => ex
229
+ # Can not fetch the version number of the new version (this happens, when it's e.g. 'Developer Rejected')
230
+ unless page.has_content?version_number
231
+ raise "Some other version was created instead of the one you defined ('#{version_number}')."
232
+ end
233
+ end
234
+ end
235
+
236
+ true
237
+ rescue Exception => ex
238
+ error_occured(ex)
239
+ end
240
+ end
241
+
242
+ # def update_app_icon!(app, path)
243
+ # raise "App icon not found at path '#{path}'" unless File.exists?(path)
244
+ # size = FastImage.size(path)
245
+ # raise "App icon must have the resolution of 1024x1024px" unless (size[0] == 1024 and size[1] == 1024)
246
+
247
+ # begin
248
+ # verify_app(app)
249
+ # open_app_page(app)
250
+
251
+ # Capybara.ignore_hidden_elements = false
252
+
253
+ # icon_area = first(:xpath, "//div[@url='tempPageContent.appIconDisplayUrl']")
254
+ # delete_button = icon_area.first(".deleteButton")
255
+ # input = icon_area.first(:xpath, ".//input[@type='file']")
256
+
257
+
258
+ # Capybara.ignore_hidden_elements = true
259
+ # first(:button, "Save").click
260
+
261
+ # rescue Exception => ex
262
+ # error_occured(ex)
263
+ # end
264
+ # end
265
+
266
+
267
+ # This will put the latest uploaded build as a new beta build
268
+ def put_build_into_beta_testing!(app, version_number)
269
+ begin
270
+ verify_app(app)
271
+ open_app_page(app)
272
+
273
+ Helper.log.info("Choosing the latest build on iTunesConnect for beta distribution")
274
+
275
+ click_on "Prerelease"
276
+
277
+ wait_for_preprocessing
278
+
279
+ if all(".switcher.ng-binding").count == 0
280
+ raise "Could not find beta build on '#{current_url}'. Make sure it is available there"
281
+ end
282
+
283
+ if first(".switcher.ng-binding")['class'].include?"checked"
284
+ Helper.log.warn("Beta Build seems to be already active. Take a look at '#{current_url}'")
285
+ return true
286
+ end
287
+
288
+ first(".switcher.ng-binding").click
289
+ if page.has_content?"Are you sure you want to start testing"
290
+ click_on "Start"
291
+ end
292
+
293
+
294
+ return true
295
+ rescue Exception => ex
296
+ error_occured(ex)
297
+ end
298
+ end
299
+
300
+ # This will choose the latest uploaded build on iTunesConnect as the production one
301
+ # After this method, you still have to call submit_for_review to actually submit the
302
+ # whole update
303
+ # @param app (Deliver::App) the app you want to choose the build for
304
+ # @param version_number (String) the version number as string for
305
+ def put_build_into_production!(app, version_number)
306
+ begin
307
+ verify_app(app)
308
+ open_app_page(app)
309
+
310
+ Helper.log.info("Choosing the latest build on iTunesConnect for release")
311
+
312
+ click_on "Prerelease"
313
+
314
+ wait_for_preprocessing
315
+
316
+ ################# Apple is finished processing the ipa file #################
317
+
318
+ Helper.log.info("Apple finally finished processing the ipa file")
319
+ open_app_page(app)
320
+
321
+ begin
322
+ first('a', :text => BUTTON_ADD_NEW_BUILD).click
323
+ wait_for_elements(".buildModalList")
324
+ sleep 1
325
+ rescue
326
+ if page.has_content?"Upload Date"
327
+ # That's fine, the ipa was already selected
328
+ return true
329
+ else
330
+ raise "Could not find Build Button. It looks like the ipa file was not properly uploaded."
331
+ end
332
+ end
333
+
334
+ if page.all('td', :text => version_number).count > 1
335
+ Helper.log.fatal "There were multiple submitted builds found. Don't know which one to choose. Just choosing the top one for now"
336
+ end
337
+
338
+ result = page.first('td', :text => version_number).first(:xpath,"./..").first(:css, ".small").click
339
+ click_on "Done" # Save the modal dialog
340
+ click_on "Save" # on the top right to save everything else
341
+
342
+ error = page.has_content?BUTTON_ADD_NEW_BUILD
343
+ raise "Could not put build itself onto production. Try opening '#{current_url}'" if error
344
+
345
+ return true
346
+ rescue Exception => ex
347
+ error_occured(ex)
348
+ end
349
+ end
350
+
351
+ # Submits the update itself to Apple, this includes the app metadata and the ipa file
352
+ # This can easily cause exceptions, which will be shown on iTC.
353
+ # @param app (Deliver::App) the app you want to submit
354
+ # @param perms (Hash) information about content rights, ...
355
+ def submit_for_review!(app, perms = nil)
356
+ begin
357
+ verify_app(app)
358
+ open_app_page(app)
359
+
360
+ Helper.log.info("Submitting app for Review")
361
+
362
+ if not page.has_content?BUTTON_STRING_SUBMIT_FOR_REVIEW
363
+ if page.has_content?WAITING_FOR_REVIEW
364
+ Helper.log.info("App is already Waiting For Review")
365
+ return true
366
+ else
367
+ raise "Couldn't find button with name '#{BUTTON_STRING_SUBMIT_FOR_REVIEW}'"
368
+ end
369
+ end
370
+
371
+ click_on BUTTON_STRING_SUBMIT_FOR_REVIEW
372
+ sleep 4
373
+
374
+ errors = (all(".pagemessage.error") || []).count > 0
375
+ raise "Some error occured when submitting the app for review: '#{current_url}'" if errors
376
+
377
+ wait_for_elements(".savingWrapper.ng-scope.ng-pristine")
378
+ if page.has_content?"Content Rights"
379
+ # Looks good.. just a few more steps
380
+
381
+ perms ||= {
382
+ export_compliance: {
383
+ encryption_updated: false,
384
+ cryptography_enabled: false,
385
+ is_exempt: false
386
+ },
387
+ third_party_content: {
388
+ contains_third_party_content: false,
389
+ has_rights: false
390
+ },
391
+ advertising_identifier: false
392
+ }
393
+
394
+ basic = "//*[@itc-radio='submitForReviewAnswers"
395
+
396
+ #####################
397
+ # Export Compliance #
398
+ #####################
399
+ if page.has_content?"Export"
400
+ if not perms[:export_compliance][:encryption_updated] and perms[:export_compliance][:cryptography_enabled]
401
+ raise "encryption_updated must be enabled if cryptography_enabled is enabled!"
402
+ end
403
+
404
+ begin
405
+ first(:xpath, "#{basic}.exportCompliance.encryptionUpdated.value' and @radio-value='#{perms[:export_compliance][:encryption_updated]}']//input").trigger('click')
406
+ first(:xpath, "#{basic}.exportCompliance.usesEncryption.value' and @radio-value='#{perms[:export_compliance][:cryptography_enabled]}']//input").trigger('click')
407
+ first(:xpath, "#{basic}.exportCompliance.isExempt.value' and @radio-value='#{perms[:export_compliance][:is_exempt]}']//input").trigger('click')
408
+ rescue
409
+ end
410
+ end
411
+
412
+ ##################
413
+ # Content Rights #
414
+ ##################
415
+ if page.has_content?"Content Rights"
416
+ if not perms[:third_party_content][:contains_third_party_content] and perms[:third_party_content][:has_rights]
417
+ raise "contains_third_party_content must be enabled if has_rights is enabled"
418
+ end
419
+
420
+ begin
421
+ first(:xpath, "#{basic}.contentRights.containsThirdPartyContent.value' and @radio-value='#{perms[:third_party_content][:contains_third_party_content]}']//input").trigger('click')
422
+ first(:xpath, "#{basic}.contentRights.hasRights.value' and @radio-value='#{perms[:third_party_content][:has_rights]}']//input").trigger('click')
423
+ rescue
424
+ end
425
+ end
426
+
427
+ ##########################
428
+ # Advertising Identifier #
429
+ ##########################
430
+ if page.has_content?"Advertising Identifier"
431
+ first(:xpath, "#{basic}.adIdInfo.usesIdfa.value' and @radio-value='#{perms[:advertising_identifier]}']//input").trigger('click') rescue nil
432
+
433
+ if perms[:advertising_identifier]
434
+ raise "Sorry, the advertising_identifier menu is not yet supported. Open '#{current_url}' in your browser and manally submit the app"
435
+ end
436
+ end
437
+
438
+
439
+ Helper.log.info("Filled out the export compliance and other information on iTC")
440
+
441
+ click_on "Submit"
442
+ sleep 5
443
+
444
+ if page.has_content?WAITING_FOR_REVIEW
445
+ # Everything worked :)
446
+ Helper.log.info("Successfully submitted App for Review".green)
447
+ return true
448
+ else
449
+ raise "So close, it looks like there went something wrong with the actual deployment. Checkout '#{current_url}'"
450
+ end
451
+ else
452
+ raise "Something is missing here."
453
+ end
454
+ return false
455
+ rescue Exception => ex
456
+ error_occured(ex)
457
+ end
458
+ end
459
+
460
+
461
+ private
462
+ def verify_app(app)
463
+ raise ItunesConnectGeneralError.new("No valid Deliver::App given") unless app.kind_of?Deliver::App
464
+ raise ItunesConnectGeneralError.new("App is missing information (apple_id not given)") unless (app.apple_id || '').to_s.length > 5
465
+ end
466
+
467
+ def error_occured(ex)
468
+ snap
469
+ raise ex # re-raise the error after saving the snapshot
470
+ end
471
+
472
+ def snap
473
+ path = "Error#{Time.now.to_i}.png"
474
+ save_screenshot(path, :full => true)
475
+ system("open '#{path}'")
476
+ end
477
+
478
+ # Since Apple takes for ages, after the upload is properly processed, we have to wait here
479
+ def wait_for_preprocessing
480
+ started = Time.now
481
+
482
+ # Wait, while iTunesConnect is processing the uploaded file
483
+ while page.has_content?"Uploaded"
484
+ # iTunesConnect is super slow... so we have to wait...
485
+ Helper.log.info("Sorry, we have to wait for iTunesConnect, since it's still processing the uploaded ipa file\n" +
486
+ "If this takes longer than 45 minutes, you have to re-upload the ipa file again.\n" +
487
+ "You can always open the browser page yourself: '#{current_url}'\n" +
488
+ "Passed time: ~#{((Time.now - started) / 60.0).to_i} minute(s)")
489
+ sleep 60
490
+ visit current_url
491
+ sleep 10
492
+ end
493
+ end
494
+
495
+ def wait_for_elements(name)
496
+ counter = 0
497
+ results = all(name)
498
+ while results.count == 0
499
+ # Helper.log.debug "Waiting for #{name}"
500
+ sleep 0.2
501
+
502
+ results = all(name)
503
+
504
+ counter += 1
505
+ if counter > 100
506
+ Helper.log.debug page.html
507
+ Helper.log.debug caller
508
+ raise ItunesConnectGeneralError.new("Couldn't find element '#{name}' after waiting for quite some time")
509
+ end
510
+ end
511
+ return results
512
+ end
513
+ end
514
+ end