Class: Deliver::ItunesConnect
- Inherits:
-
Object
- Object
- Deliver::ItunesConnect
- Includes:
- Capybara::DSL
- Defined in:
- lib/deliver/itunes_connect/itunes_connect.rb,
lib/deliver/itunes_connect/itunes_connect_login.rb,
lib/deliver/itunes_connect/itunes_connect_helper.rb,
lib/deliver/itunes_connect/itunes_connect_reader.rb,
lib/deliver/itunes_connect/itunes_connect_app_icon.rb,
lib/deliver/itunes_connect/itunes_connect_additional.rb,
lib/deliver/itunes_connect/itunes_connect_app_rating.rb,
lib/deliver/itunes_connect/itunes_connect_submission.rb,
lib/deliver/itunes_connect/itunes_connect_new_version.rb
Overview
Everything related to submitting the app
Defined Under Namespace
Classes: ItunesConnectGeneralError, ItunesConnectLoginError
Constant Summary collapse
- ITUNESCONNECT_URL =
"https://itunesconnect.apple.com/"
- APP_DETAILS_URL =
"https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/[[app_id]]"
- BUTTON_STRING_NEW_VERSION =
"New Version"
- BUTTON_STRING_SUBMIT_FOR_REVIEW =
"Submit for Review"
- BUTTON_ADD_NEW_BUILD =
'Click + to add a build before you submit your app.'
- WAITING_FOR_REVIEW =
"Waiting For Review"
- PROCESSING_TEXT =
"Processing"
Instance Method Summary collapse
-
#create_new_version!(app, version_number) ⇒ Object
This method creates a new version of your app using the iTunesConnect frontend.
-
#get_app_status(app) ⇒ Object
This method will fetch the current status (App::AppStatus) of your app and return it.
-
#get_live_version(app) ⇒ Object
This method will fetch the version number of the currently live version of your app and return it.
-
#initialize ⇒ ItunesConnect
constructor
A new instance of ItunesConnect.
-
#login(user = nil, password = nil) ⇒ bool
Loggs in a user with the given login data on the iTC Frontend.
-
#put_build_into_beta_testing!(app, version_number) ⇒ Object
This will put the latest uploaded build as a new beta build.
-
#put_build_into_production!(app, version_number) ⇒ Object
This will choose the latest uploaded build on iTunesConnect as the production one After this method, you still have to call submit_for_review to actually submit the whole update.
-
#set_app_rating!(app, path_to_json) ⇒ Object
Setting the app’s age restrictions.
- #set_app_review_information!(app, hash) ⇒ Object
- #set_categories!(app, primary, secondary) ⇒ Object
-
#set_copyright!(app, text) ⇒ Object
This file sets additional information like copyright and age rating.
- #set_release_after_approval!(app, automatic_release) ⇒ Object
-
#submit_for_review!(app, perms = nil) ⇒ Object
Submits the update itself to Apple, this includes the app metadata and the ipa file This can easily cause exceptions, which will be shown on iTC.
- #update_existing_version_number!(app, version_number) ⇒ Object
-
#upload_app_icon!(app, path) ⇒ Object
Uploading a new full size app icon.
Constructor Details
#initialize ⇒ ItunesConnect
Returns a new instance of ItunesConnect.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/deliver/itunes_connect/itunes_connect.rb', line 43 def initialize super return if Helper.is_test? DependencyChecker.check_dependencies Capybara.run_server = false Capybara.default_driver = :poltergeist Capybara.javascript_driver = :poltergeist Capybara.current_driver = :poltergeist Capybara.app_host = ITUNESCONNECT_URL # Since Apple has some SSL errors, we have to configure the client properly: # https://github.com/ariya/phantomjs/issues/11239 Capybara.register_driver :poltergeist do |a| conf = ['--debug=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1'] Capybara::Poltergeist::Driver.new(a, { phantomjs_options: conf, phantomjs_logger: File.open("/tmp/poltergeist_log.txt", "a"), js_errors: false }) end page.driver.headers = { "Accept-Language" => "en" } login end |
Instance Method Details
#create_new_version!(app, version_number) ⇒ Object
This method creates a new version of your app using the iTunesConnect frontend. This will happen directly after calling this method. the new version that should be created
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/deliver/itunes_connect/itunes_connect_new_version.rb', line 9 def create_new_version!(app, version_number) begin current_version = get_live_version(app) verify_app(app) open_app_page(app) if page.has_content?BUTTON_STRING_NEW_VERSION if current_version == version_number # This means, this version is already live on the App Store raise "Version #{version_number} is already created, submitted and released on iTC. Please verify you're using a new version number." end click_on BUTTON_STRING_NEW_VERSION Helper.log.info "Creating a new version (#{version_number})" all(".fullWidth.nobottom.ng-isolate-scope.ng-pristine").last.set(version_number.to_s) click_on "Create" while not page.has_content?"Prepare for Submission" sleep 1 Helper.log.debug("Waiting for 'Prepare for Submission'") end else Helper.log.warn "Can not create version #{version_number} on iTunesConnect. Maybe it was already created." begin created_version = wait_for_elements("input[ng-model='versionInfo.version.value']").first.value if created_version != version_number update_existing_version_number!(app, version_number) end rescue => ex # Can not fetch the version number of the new version (this happens, when it's e.g. 'Developer Rejected') Helper.log.error ex unless page.has_content?version_number raise "Some other version was created instead of the one you defined ('#{version_number}')." end end end true rescue => ex error_occured(ex) end end |
#get_app_status(app) ⇒ Object
This method will fetch the current status (App::AppStatus) of your app and return it. This method uses a headless browser under the hood, so it might take some time until you get the result
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/deliver/itunes_connect/itunes_connect_reader.rb', line 11 def get_app_status(app) begin verify_app(app) open_app_page(app) if page.has_content?WAITING_FOR_REVIEW # That's either Upload Received or Waiting for Review if page.has_content?"To submit a new build, you must remove this version from review" return App::AppStatus::WAITING_FOR_REVIEW else return App::AppStatus::UPLOAD_RECEIVED end elsif page.has_content?BUTTON_STRING_NEW_VERSION return App::AppStatus::READY_FOR_SALE elsif page.has_content?BUTTON_STRING_SUBMIT_FOR_REVIEW return App::AppStatus::PREPARE_FOR_SUBMISSION else raise "App status not yet implemented" end rescue Exception => ex error_occured(ex) end end |
#get_live_version(app) ⇒ Object
This method will fetch the version number of the currently live version of your app and return it. This method uses a headless browser under the hood, so it might take some time until you get the result
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/deliver/itunes_connect/itunes_connect_reader.rb', line 43 def get_live_version(app) begin verify_app(app) open_app_page(app) begin version_number = wait_for_elements("input[ng-model='versionInfo.version.value']").first.value version_number ||= first(".status.ready").text.split(" ").first return version_number rescue Helper.log.debug "Could not fetch version number of the live version for app #{app}." return nil end rescue => ex error_occured(ex) end end |
#login(user = nil, password = nil) ⇒ bool
Loggs in a user with the given login data on the iTC Frontend. You don’t need to pass a username and password. It will Automatically be fetched using the CredentialsManager::PasswordManager. This method will also automatically be called when triggering other actions like #open_app_page
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/deliver/itunes_connect/itunes_connect_login.rb', line 15 def login(user = nil, password = nil) Helper.log.info "Logging into iTunesConnect" user ||= CredentialsManager::PasswordManager.shared_manager.username password ||= CredentialsManager::PasswordManager.shared_manager.password result = visit ITUNESCONNECT_URL raise "Could not open iTunesConnect" unless result['status'] == 'success' sleep 3 if page.has_content?"My Apps" # Already logged in return true end begin wait_for_elements('#accountpassword') rescue => ex # when the user is already logged in, this will raise an exception end fill_in "accountname", with: user fill_in "accountpassword", with: password begin (wait_for_elements(".enabled").first.click rescue nil) # Login Button wait_for_elements('.homepageWrapper.ng-scope') if page.has_content?"My Apps" # Everything looks good else raise ItunesConnectLoginError.new("Looks like your login data was correct, but you do not have access to the apps.") end rescue => ex Helper.log.debug(ex) raise ItunesConnectLoginError.new("Error logging in user #{user} with the given password. Make sure you entered them correctly.") end Helper.log.info "Successfully logged into iTunesConnect" true rescue => ex error_occured(ex) end |
#put_build_into_beta_testing!(app, version_number) ⇒ Object
This will put the latest uploaded build as a new beta build
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/deliver/itunes_connect/itunes_connect_submission.rb', line 6 def put_build_into_beta_testing!(app, version_number) begin verify_app(app) open_app_page(app) Helper.log.info("Choosing the latest build on iTunesConnect for beta distribution") sleep 3 click_on "Prerelease" sleep 3 wait_for_preprocessing # Beta Switches if all(".switcher.ng-binding.checked").count == 0 raise "Looks like Beta Testing is not yet enabled for this app. Open '#{current_url}' and enable TestFlight Beta Testing.".red end if all(".bt-version > a").count == 0 raise "Couldn't find any builds. Please check the iTunes Conncet page: '#{current_url}'".red end first(".bt-version > a").click email = wait_for_elements("input[ng-model='testinfo.data.details[currentLoc].feedbackEmail.value']").first if email.value.to_s.length == 0 # Some value is needed to actually distribute the beta version email.set CredentialsManager::PasswordManager.shared_manager.username click_on "Save" end Helper.log.info "Successfully enabled latest beta build.".green return true rescue => ex error_occured(ex) end end |
#put_build_into_production!(app, version_number) ⇒ Object
This will choose the latest uploaded build on iTunesConnect as the production one After this method, you still have to call submit_for_review to actually submit the whole update
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/deliver/itunes_connect/itunes_connect_submission.rb', line 50 def put_build_into_production!(app, version_number) begin verify_app(app) open_app_page(app) Helper.log.info("Choosing the latest build on iTunesConnect for release") click_on "Prerelease" wait_for_preprocessing ################# Apple is finished processing the ipa file ################# Helper.log.info("Apple finally finished processing the ipa file") open_app_page(app) begin first('a', :text => BUTTON_ADD_NEW_BUILD).click wait_for_elements(".buildModalList") sleep 5 rescue if page.has_content?"Upload Date" # That's fine, the ipa was already selected return true else raise "Could not find Build Button. It looks like the ipa file was not properly uploaded." end end if page.all('td', :text => version_number).count > 1 Helper.log.fatal "There were multiple submitted builds found. Don't know which one to choose. Just choosing the top one for now" end result = page.first('td', :text => version_number).first(:xpath,"./..").first(:css, ".small").click click_on "Done" # Save the modal dialog click_on "Save" # on the top right to save everything else error = page.has_content?BUTTON_ADD_NEW_BUILD raise "Could not put build itself onto production. Try opening '#{current_url}'" if error return true rescue => ex error_occured(ex) end end |
#set_app_rating!(app, path_to_json) ⇒ Object
Setting the app’s age restrictions
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/deliver/itunes_connect/itunes_connect_app_rating.rb', line 7 def (app, path_to_json) path_to_json = File.(path_to_json) raise "Could not find app rating JSON file" unless File.exists?(path_to_json) config = JSON.parse(File.read(path_to_json)) verify_app(app) open_app_page(app) Helper.log.info "Updating the app's rating".green first("a[ng-show='versionInfo.ratings.isEditable']").click # open the ratings screen rows = wait_for_elements(".defaultTable.ratingsTable > tbody > tr.ng-scope") # .ng-scope, since there is one empty row if rows.count != config.count raise "The number of options passed in the config file does not match the number of options available on iTC!".red end # Setting all the values based on config file rows.each_with_index do |row, index| current = config[index] level = name_for_level(current['level'], current['type'] == 'boolean') Helper.log.info "Setting '#{current['comment']}' to #{level}.".green radio_value = "ITC.apps.ratings.level.#{level}" row.first("td > div[radio-value='#{radio_value}']").click end # Check if there is a warning or error message because of this rating = first("p[ng-show='tempPageContent.ratingDialog.showErrorMessage']") Helper.log.error .text if Helper.log.info "Finished setting updated app rating" (click_on "Done" rescue nil) (click_on "Save" rescue nil) # if nothing has changed, there is no back button and we don't care rescue => ex error_occured(ex) end |
#set_app_review_information!(app, hash) ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/deliver/itunes_connect/itunes_connect_additional.rb', line 18 def set_app_review_information!(app, hash) verify_app(app) open_app_page(app) Helper.log.info "Setting review information: #{hash}" first("input[ng-model='versionInfo.appReviewInfo.firstName.value']").set hash[:first_name] first("input[ng-model='versionInfo.appReviewInfo.lastName.value']").set hash[:last_name] first("input[ng-model='versionInfo.appReviewInfo.phoneNumber.value']").set hash[:phone_number] first("input[ng-model='versionInfo.appReviewInfo.emailAddress.value']").set hash[:email_address] first("input[ng-model='versionInfo.appReviewInfo.userName.value']").set hash[:demo_user] first("input[ng-model='versionInfo.appReviewInfo.password.value']").set hash[:demo_password] first("span[ng-show='versionInfo.appReviewInfo.reviewNotes.isEditable'] > * > textarea").set hash[:notes] (click_on "Save" rescue nil) # if nothing has changed, there is no back button and we don't care Helper.log.info "Successfully saved review information".green rescue => ex error_occured(ex) end |
#set_categories!(app, primary, secondary) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/deliver/itunes_connect/itunes_connect_additional.rb', line 53 def set_categories!(app, primary, secondary) verify_app(app) open_app_page(app) Helper.log.info "Setting primary/secondary category.'".green if primary all("select[ng-model='versionInfo.primaryCategory.value'] > option").each do |category| if category.text.to_s == primary.to_s category.select_option primary = nil break end end if primary Helper.log.info "Could not find category '#{primary}'. Make sure it's available on iTC".red end end if secondary all("select[ng-model='versionInfo.secondaryCategory.value'] > option").each do |category| if category.text.to_s == secondary.to_s category.select_option secondary = nil break end end if secondary Helper.log.info "Could not find category '#{secondary}'. Make sure it's available on iTC".red end end (click_on "Save" rescue nil) # if nothing has changed, there is no back button and we don't care rescue => ex error_occured(ex) end |
#set_copyright!(app, text) ⇒ Object
This file sets additional information like copyright and age rating
5 6 7 8 9 10 11 12 13 14 15 16 |
# File 'lib/deliver/itunes_connect/itunes_connect_additional.rb', line 5 def set_copyright!(app, text) verify_app(app) open_app_page(app) Helper.log.info "Setting copyright to '#{text}'".green first("input[ng-model='versionInfo.copyright.value']").set text (click_on "Save" rescue nil) # if nothing has changed, there is no back button and we don't care rescue => ex error_occured(ex) end |
#set_release_after_approval!(app, automatic_release) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/deliver/itunes_connect/itunes_connect_additional.rb', line 39 def set_release_after_approval!(app, automatic_release) verify_app(app) open_app_page(app) Helper.log.info "Setting automatic release to '#{automatic_release}'".green # Find the correct radio button first("div[itc-radio='versionInfo.releaseOnApproval.value'][radio-value='#{automatic_release.to_s}'] > * > a").click (click_on "Save" rescue nil) # if nothing has changed, there is no back button and we don't care rescue => ex error_occured(ex) end |
#submit_for_review!(app, perms = nil) ⇒ Object
Submits the update itself to Apple, this includes the app metadata and the ipa file This can easily cause exceptions, which will be shown on iTC.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/deliver/itunes_connect/itunes_connect_submission.rb', line 100 def submit_for_review!(app, perms = nil) begin verify_app(app) open_app_page(app) Helper.log.info("Submitting app for Review") if not page.has_content?BUTTON_STRING_SUBMIT_FOR_REVIEW if page.has_content?WAITING_FOR_REVIEW Helper.log.info("App is already Waiting For Review") return true else raise "Couldn't find button with name '#{BUTTON_STRING_SUBMIT_FOR_REVIEW}'!" end end click_on BUTTON_STRING_SUBMIT_FOR_REVIEW sleep 4 errors = (all(".pagemessage.error") || []).count > 0 raise "Some error occured when submitting the app for review: '#{current_url}'" if errors wait_for_elements(".savingWrapper.ng-scope.ng-pristine") wait_for_elements(".radiostyle") sleep 3 if page.has_content?"Content Rights" # Looks good.. just a few more steps perms ||= { export_compliance: { encryption_updated: false, cryptography_enabled: false, is_exempt: false }, third_party_content: { contains_third_party_content: false, has_rights: false }, advertising_identifier: { use_idfa: false, serve_advertisement: false, attribute_advertisement: false, attribute_actions: false, limit_ad_tracking: false } } basic = "//*[@itc-radio='submitForReviewAnswers" checkbox = "//*[@itc-checkbox='submitForReviewAnswers" ##################### # Export Compliance # ##################### if page.has_content?"Export" if not perms[:export_compliance][:encryption_updated] and perms[:export_compliance][:cryptography_enabled] raise "encryption_updated must be enabled if cryptography_enabled is enabled!" end begin encryption_updated_control = all(:xpath, "#{basic}.exportCompliance.encryptionUpdated.value' and @radio-value='#{perms[:export_compliance][:encryption_updated]}']//input") encryption_updated_control[0].trigger('click') if encryption_updated_control.count > 0 first(:xpath, "#{basic}.exportCompliance.usesEncryption.value' and @radio-value='#{perms[:export_compliance][:cryptography_enabled]}']//input").trigger('click') first(:xpath, "#{basic}.exportCompliance.isExempt.value' and @radio-value='#{perms[:export_compliance][:is_exempt]}']//input").trigger('click') rescue end end ################## # Content Rights # ################## if page.has_content?"Content Rights" if not perms[:third_party_content][:contains_third_party_content] and perms[:third_party_content][:has_rights] raise "contains_third_party_content must be enabled if has_rights is enabled".red end begin first(:xpath, "#{basic}.contentRights.containsThirdPartyContent.value' and @radio-value='#{perms[:third_party_content][:contains_third_party_content]}']//input").trigger('click') first(:xpath, "#{basic}.contentRights.hasRights.value' and @radio-value='#{perms[:third_party_content][:has_rights]}']//input").trigger('click') rescue end end ########################## # Advertising Identifier # ########################## if page.has_content?"Advertising Identifier" first(:xpath, "#{basic}.adIdInfo.usesIdfa.value' and @radio-value='#{perms[:advertising_identifier][:use_idfa]}']//a").click rescue nil if perms[:advertising_identifier][:use_idfa] if perms[:advertising_identifier][:serve_advertisement] first(:xpath, "#{checkbox}.adIdInfo.servesAds.value']//a").click end if perms[:advertising_identifier][:attribute_advertisement] first(:xpath, "#{checkbox}.adIdInfo.tracksInstall.value']//a").click end if perms[:advertising_identifier][:attribute_actions] first(:xpath, "#{checkbox}.adIdInfo.tracksAction.value']//a").click end if perms[:advertising_identifier][:limit_ad_tracking] first(:xpath, "#{checkbox}.adIdInfo.limitsTracking.value']//a").click end end end Helper.log.info("Filled out the export compliance and other information on iTC".green) click_on "Submit" sleep 5 if page.has_content?WAITING_FOR_REVIEW # Everything worked :) Helper.log.info("Successfully submitted App for Review".green) return true else raise "So close, it looks like there went something wrong with the actual deployment. Checkout '#{current_url}'".red end else raise "Something is missing here.".red end return false rescue => ex error_occured(ex) end end |
#update_existing_version_number!(app, version_number) ⇒ Object
57 58 59 60 61 62 63 64 65 |
# File 'lib/deliver/itunes_connect/itunes_connect_new_version.rb', line 57 def update_existing_version_number!(app, version_number) Helper.log.warn "There is already a new version created, which does not match the current version: '#{version_number}'" version_input = wait_for_elements("input[ng-model='versionInfo.version.value']").first version_input.set version_number click_on "Save" Helper.log.warn "Changed the version number of the latest version to '#{version_number}'".green end |
#upload_app_icon!(app, path) ⇒ Object
Uploading a new full size app icon
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/deliver/itunes_connect/itunes_connect_app_icon.rb', line 7 def upload_app_icon!(app, path) path = File.(path) raise "Could not find app icon at path '#{path}'".red unless File.exists?path size = FastImage.size(path) raise "App icon must have the resolution of 1024x1024px".red unless (size[0] == 1024 and size[1] == 1024) begin verify_app(app) open_app_page(app) Helper.log.info "Starting upload of new app icon".green evaluate_script("$('.appversionicon > .ios7-style-icon').prev().click()") # delete button evaluate_script("$('[style-class=\"appversionicon rounded\"] [itc-launch-filechooser] + input').attr('id', 'deliverFileUploadInput')") # set div evaluate_script("URL = webkitURL; URL.createObjectURL = function(){return 'blob:abc'}"); # shim URL page.attach_file("deliverFileUploadInput", path) # add file sleep 10 click_on "Save" Helper.log.info "Finished uploading the new app icon".green rescue => ex error_occured(ex) end end |