fastlane 2.54.0.beta.20170822010003 → 2.54.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/deliver/lib/deliver/loader.rb +29 -3
  4. data/deliver/lib/deliver/upload_metadata.rb +18 -0
  5. data/fastlane/lib/fastlane/actions/opt_out_crash_reporting.rb +1 -1
  6. data/fastlane/lib/fastlane/version.rb +1 -1
  7. data/fastlane_core/lib/fastlane_core.rb +1 -0
  8. data/fastlane_core/lib/fastlane_core/crash_reporter/crash_reporter.rb +1 -1
  9. data/fastlane_core/lib/fastlane_core/device_manager.rb +12 -12
  10. data/fastlane_core/lib/fastlane_core/test_parser.rb +145 -0
  11. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +1 -1
  12. data/pilot/lib/pilot/tester_manager.rb +8 -1
  13. data/sigh/lib/sigh/runner.rb +27 -18
  14. data/snapshot/README.md +3 -1
  15. data/snapshot/lib/assets/SnapshotHelper.swift +57 -35
  16. data/snapshot/lib/assets/SnapshotHelperXcode8.swift +173 -0
  17. data/snapshot/lib/snapshot.rb +6 -0
  18. data/snapshot/lib/snapshot/collector.rb +37 -11
  19. data/snapshot/lib/snapshot/detect_values.rb +4 -1
  20. data/snapshot/lib/snapshot/fixes/simulator_zoom_fix.rb +1 -1
  21. data/snapshot/lib/snapshot/options.rb +5 -0
  22. data/snapshot/lib/snapshot/reports_generator.rb +34 -3
  23. data/snapshot/lib/snapshot/runner.rb +30 -215
  24. data/snapshot/lib/snapshot/setup.rb +9 -3
  25. data/snapshot/lib/snapshot/simulator_launchers/launcher_configuration.rb +53 -0
  26. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb +203 -0
  27. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +129 -0
  28. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_xcode_8.rb +110 -0
  29. data/snapshot/lib/snapshot/test_command_generator.rb +39 -107
  30. data/snapshot/lib/snapshot/test_command_generator_base.rb +94 -0
  31. data/snapshot/lib/snapshot/test_command_generator_xcode_8.rb +65 -0
  32. data/snapshot/lib/snapshot/update.rb +4 -2
  33. data/spaceship/lib/spaceship/portal/provisioning_profile.rb +5 -2
  34. data/spaceship/lib/spaceship/test_flight/client.rb +8 -0
  35. data/spaceship/lib/spaceship/test_flight/tester.rb +20 -4
  36. metadata +26 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32ceeaf72e899a6e33a12cf6218061c5b9297f3a
4
- data.tar.gz: 4c6121a674608a8268e8354281519077e37cd699
3
+ metadata.gz: 4b0e5057dd038fe51f2566e6e003e9bb6ffea30f
4
+ data.tar.gz: 21be6de01c31164ab160e45685a9983123341ed4
5
5
  SHA512:
6
- metadata.gz: 7ec6be6e424f85fd7d7fc4619fff8c6bf86027329f95ea680c2f8f8bf478d0c486ec1029d61bc8b789eb10f1a1d03bdc38f15783c2cb2463e915f69c20a25543
7
- data.tar.gz: dc9b54ddd7e1817bc6811d494887cd12d2c07795ce6062f02fe31b14f3a9e3fe663de00a2e4b16ad58baeecce82fb33fa596472ab267478a7b6e1c67cd2c8cbc
6
+ metadata.gz: 4aec5f5ee4a8183d8b2a296822e9afb8c04e296c1068114b60d46dc25b085ad5ff42bb5a59581e6b15a1e8ea4c99ee5da5cffd5f15daf3a5ff2c6f602551525c
7
+ data.tar.gz: 5e9a703ed1d1834d6d2e5e98f042552775dab51ace991a739faf7f16683ff264c4ee0c6775099c4f3cc45afb29d40397da6bf5619ae75df6c82092fa201472f1
data/README.md CHANGED
@@ -161,7 +161,7 @@ You can easily opt-out of metrics collection by adding `opt_out_usage` at the to
161
161
 
162
162
  ## Crash Reporting
163
163
 
164
- In order to continuously improve stability, _fastlane_ will record crash reports with sanitized stacktraces. Sanitization removes personal information from the stacktrace and error message (including home directories, _fastlane_ path, gem paths, environment variables, and parameters).
164
+ In order to continuously improve stability, _fastlane_ will record crash reports with sanitized stack traces. Sanitization removes personal information from the stack trace and error message (including home directories, _fastlane_ path, gem paths, environment variables, and parameters).
165
165
 
166
166
  You can easily opt-out of crash reporting by adding `opt_out_crash_reporting` at the top of your `Fastfile` or by setting the environment variable `FASTLANE_OPT_OUT_CRASH_REPORTING`. Just like metrics mentioned above, participating helps us provide the best possible support for _fastlane_, so we hope you'll consider it a plus! :heavy_plus_sign:
167
167
 
@@ -7,12 +7,38 @@ module Deliver
7
7
  APPLE_TV_DIR_NAME = "appleTV".freeze
8
8
  IMESSAGE_DIR_NAME = "iMessage".freeze
9
9
  DEFAULT_DIR_NAME = "default".freeze
10
- ALL_LANGUAGES = (FastlaneCore::Languages::ALL_LANGUAGES + [APPLE_TV_DIR_NAME, APPLE_TV_DIR_NAME, IMESSAGE_DIR_NAME, DEFAULT_DIR_NAME]).map(&:downcase).freeze
10
+
11
+ SPECIAL_DIR_NAMES = [APPLE_TV_DIR_NAME, IMESSAGE_DIR_NAME, DEFAULT_DIR_NAME].freeze
12
+
13
+ EXCEPTION_DIRECTORIES = UploadMetadata::ALL_META_SUB_DIRS.map(&:downcase).freeze
11
14
 
12
15
  def self.language_folders(root)
13
- Dir.glob(File.join(root, '*')).select do |path|
14
- File.directory?(path) && ALL_LANGUAGES.include?(File.basename(path).downcase)
16
+ folders = Dir.glob(File.join(root, '*'))
17
+
18
+ if Helper.is_test?
19
+ available_languages = FastlaneCore::Languages::ALL_LANGUAGES
20
+ else
21
+ available_languages = Spaceship::Tunes.client.available_languages.sort
22
+ end
23
+
24
+ allowed_directory_names = (available_languages + SPECIAL_DIR_NAMES).map(&:downcase).freeze
25
+
26
+ selected_folders = folders.select do |path|
27
+ File.directory?(path) && allowed_directory_names.include?(File.basename(path).downcase)
15
28
  end.sort
29
+
30
+ # Gets list of folders that are not supported languages
31
+ rejected_folders = folders.select do |path|
32
+ normalized_path = File.basename(path).downcase
33
+ File.directory?(path) && !allowed_directory_names.include?(normalized_path) && !EXCEPTION_DIRECTORIES.include?(normalized_path)
34
+ end.sort
35
+
36
+ unless rejected_folders.empty?
37
+ rejected_folders = rejected_folders.map { |path| File.basename(path) }
38
+ UI.user_error! "Unsupport directory name(s) for screenshots/metadata: #{rejected_folders.join(', ')}\n\nValid directory names are: #{allowed_directory_names}"
39
+ end
40
+
41
+ selected_folders
16
42
  end
17
43
  end
18
44
  end
@@ -55,6 +55,8 @@ module Deliver
55
55
  # Directory name it contains review information
56
56
  REVIEW_INFORMATION_DIR = "review_information"
57
57
 
58
+ ALL_META_SUB_DIRS = [TRADE_REPRESENTATIVE_CONTACT_INFORMATION_DIR, REVIEW_INFORMATION_DIR]
59
+
58
60
  # rubocop:disable Metrics/PerceivedComplexity
59
61
 
60
62
  # Make sure to call `load_from_filesystem` before calling upload
@@ -146,6 +148,22 @@ module Deliver
146
148
  # Build a complete list of the required languages
147
149
  enabled_languages = detect_languages(options)
148
150
 
151
+ # Get all languages used in existing settings
152
+ (LOCALISED_VERSION_VALUES + LOCALISED_APP_VALUES).each do |key|
153
+ current = options[key]
154
+ next unless current && current.kind_of?(Hash)
155
+ current.each do |language, value|
156
+ enabled_languages << language unless enabled_languages.include?(language)
157
+ end
158
+ end
159
+
160
+ # Check folder list (an empty folder signifies a language is required)
161
+ Loader.language_folders(options[:metadata_path]).each do |lang_folder|
162
+ next unless File.directory?(lang_folder) # We don't want to read txt as they are non localised
163
+ language = File.basename(lang_folder)
164
+ enabled_languages << language unless enabled_languages.include?(language)
165
+ end
166
+
149
167
  return unless enabled_languages.include?("default")
150
168
  UI.message("Detected languages: " + enabled_languages.to_s)
151
169
 
@@ -13,7 +13,7 @@ module Fastlane
13
13
  def self.details
14
14
  [
15
15
  "By default, fastlane will send a report when it crashes",
16
- "The stacktrace is sanitized so no personal information is sent.",
16
+ "The stack trace is sanitized so no personal information is sent.",
17
17
  "Learn more at https://github.com/fastlane/fastlane#crash-reporting",
18
18
  "Add `opt_out_crash_reporting` at the top of your Fastfile to disable crash reporting"
19
19
  ].join(' ')
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
- VERSION = '2.54.0.beta.20170822010003'.freeze
2
+ VERSION = '2.54.0'.freeze
3
3
  DESCRIPTION = "The easiest way to automate beta deployments and releases for your iOS and Android apps".freeze
4
4
  MINIMUM_XCODE_RELEASE = "7.0".freeze
5
5
  end
@@ -39,6 +39,7 @@ require 'fastlane_core/ui/errors/fastlane_error'
39
39
  require 'fastlane_core/ui/errors/fastlane_crash'
40
40
  require 'fastlane_core/ui/errors/fastlane_shell_error'
41
41
  require 'fastlane_core/ui/errors/fastlane_common_error'
42
+ require 'fastlane_core/test_parser'
42
43
 
43
44
  # Third Party code
44
45
  require 'colored'
@@ -55,7 +55,7 @@ module FastlaneCore
55
55
 
56
56
  def show_message
57
57
  UI.message("Sending crash report...")
58
- UI.message("The stacktrace is sanitized so no personal information is sent.")
58
+ UI.message("The stack trace is sanitized so no personal information is sent.")
59
59
  UI.message("To see what we are sending, look here: #{crash_report_path}")
60
60
  UI.message("Learn more at https://github.com/fastlane/fastlane#crash-reporting")
61
61
  UI.message("You can disable crash reporting by adding `opt_out_crash_reporting` at the top of your Fastfile")
@@ -25,22 +25,22 @@ module FastlaneCore
25
25
  end
26
26
 
27
27
  output.split(/\n/).each do |line|
28
+ next if line =~ /unavailable/
28
29
  next if line =~ /^== /
29
30
  if line =~ /^-- /
30
31
  (os_type, os_version) = line.gsub(/-- (.*) --/, '\1').split
31
32
  else
32
- # iPad 2 (0EDE6AFC-3767-425A-9658-AAA30A60F212) (Shutdown)
33
- # iPad Air 2 (4F3B8059-03FD-4D72-99C0-6E9BBEE2A9CE) (Shutdown) (unavailable, device type profile not found)
34
- if line.include?("inch)")
35
- # For Xcode 8, where sometimes we have the # of inches in ()
36
- # iPad Pro (12.9 inch) (CEF11EB3-79DF-43CB-896A-0F33916C8BDE) (Shutdown)
37
- match = line.match(/\s+([^\(]+ \(.*inch\)) \(([-0-9A-F]+)\) \(([^\(]+)\)(.*unavailable.*)?/)
38
- else
39
- match = line.match(/\s+([^\(]+) \(([-0-9A-F]+)\) \(([^\(]+)\)(.*unavailable.*)?/)
40
- end
41
33
 
42
- if match && !match[4] && (os_type == requested_os_type || requested_os_type == "")
43
- @devices << Device.new(name: match[1], os_type: os_type, os_version: os_version, udid: match[2], state: match[3], is_simulator: true)
34
+ # " iPad (5th generation) (852A5796-63C3-4641-9825-65EBDC5C4259) (Shutdown)"
35
+ # This line will turn the above string into
36
+ # ["iPad", "(5th generation)", "(852A5796-63C3-4641-9825-65EBDC5C4259)", "(Shutdown)"]
37
+ matches = line.strip.scan(/(.*?) (\(.*?\))/).flatten.reject(&:empty?)
38
+ state = matches.pop.to_s.delete('(').delete(')')
39
+ udid = matches.pop.to_s.delete('(').delete(')')
40
+ name = matches.join(' ')
41
+
42
+ if matches.count && (os_type == requested_os_type || requested_os_type == "")
43
+ @devices << Device.new(name: name, os_type: os_type, os_version: os_version, udid: udid, state: state, is_simulator: true)
44
44
  end
45
45
  end
46
46
  end
@@ -254,7 +254,7 @@ module FastlaneCore
254
254
 
255
255
  logarchive_dst = Shellwords.escape(File.join(logs_destination_dir, "system_logs-#{log_identity}.logarchive"))
256
256
  FileUtils.rm_rf(logarchive_dst)
257
- FileUtils.mkdir_p(logarchive_dst)
257
+ FileUtils.mkdir_p(File.expand_path("..", logarchive_dst))
258
258
  command = "xcrun simctl spawn #{device.udid} log collect --output #{logarchive_dst} 2>/dev/null"
259
259
  FastlaneCore::CommandExecutor.execute(command: command, print_all: false, print_command: true)
260
260
  end
@@ -0,0 +1,145 @@
1
+ module FastlaneCore
2
+ class TestParser
3
+ attr_accessor :data
4
+
5
+ attr_accessor :file_content
6
+
7
+ attr_accessor :raw_json
8
+
9
+ # Returns a hash with the path being the key, and the value
10
+ # defining if the tests were successful
11
+ def self.auto_convert(config)
12
+ FastlaneCore::PrintTable.print_values(config: config,
13
+ title: "Summary for trainer #{Trainer::VERSION}")
14
+
15
+ containing_dir = config[:path]
16
+ files = Dir["#{containing_dir}/**/Logs/Test/*TestSummaries.plist"]
17
+ files += Dir["#{containing_dir}/Test/*TestSummaries.plist"]
18
+ files += Dir["#{containing_dir}/*TestSummaries.plist"]
19
+ files += Dir[containing_dir] if containing_dir.end_with?(".plist") # if it's the exact path to a plist file
20
+
21
+ if files.empty?
22
+ UI.user_error!("No test result files found in directory '#{containing_dir}', make sure the file name ends with 'TestSummaries.plist'")
23
+ end
24
+
25
+ return_hash = {}
26
+ files.each do |path|
27
+ if config[:output_directory]
28
+ FileUtils.mkdir_p(config[:output_directory])
29
+ filename = File.basename(path).gsub(".plist", config[:extension])
30
+ to_path = File.join(config[:output_directory], filename)
31
+ else
32
+ to_path = path.gsub(".plist", config[:extension])
33
+ end
34
+
35
+ tp = Trainer::TestParser.new(path)
36
+ File.write(to_path, tp.to_junit)
37
+ puts "Successfully generated '#{to_path}'"
38
+
39
+ return_hash[to_path] = tp.tests_successful?
40
+ end
41
+ return_hash
42
+ end
43
+
44
+ def initialize(path)
45
+ path = File.expand_path(path)
46
+ UI.user_error!("File not found at path '#{path}'") unless File.exist?(path)
47
+
48
+ self.file_content = File.read(path)
49
+ self.raw_json = Plist.parse_xml(self.file_content)
50
+ return if self.raw_json["FormatVersion"].to_s.length.zero? # maybe that's a useless plist file
51
+
52
+ ensure_file_valid!
53
+ parse_content
54
+ end
55
+
56
+ # Returns the JUnit report as String
57
+ def to_junit
58
+ JunitGenerator.new(self.data).generate
59
+ end
60
+
61
+ # @return [Bool] were all tests successful? Is false if at least one test failed
62
+ def tests_successful?
63
+ self.data.collect { |a| a[:number_of_failures] }.all?(&:zero?)
64
+ end
65
+
66
+ private
67
+
68
+ def ensure_file_valid!
69
+ format_version = self.raw_json["FormatVersion"]
70
+ supported_versions = ["1.1", "1.2"]
71
+ UI.user_error!("Format version '#{format_version}' is not supported, must be #{supported_versions.join(', ')}") unless supported_versions.include?(format_version)
72
+ end
73
+
74
+ # Converts the raw plist test structure into something that's easier to enumerate
75
+ def unfold_tests(data)
76
+ # `data` looks like this
77
+ # => [{"Subtests"=>
78
+ # [{"Subtests"=>
79
+ # [{"Subtests"=>
80
+ # [{"Duration"=>0.4,
81
+ # "TestIdentifier"=>"Unit/testExample()",
82
+ # "TestName"=>"testExample()",
83
+ # "TestObjectClass"=>"IDESchemeActionTestSummary",
84
+ # "TestStatus"=>"Success",
85
+ # "TestSummaryGUID"=>"4A24BFED-03E6-4FBE-BC5E-2D80023C06B4"},
86
+ # {"FailureSummaries"=>
87
+ # [{"FileName"=>"/Users/krausefx/Developer/themoji/Unit/Unit.swift",
88
+ # "LineNumber"=>34,
89
+ # "Message"=>"XCTAssertTrue failed - ",
90
+ # "PerformanceFailure"=>false}],
91
+ # "TestIdentifier"=>"Unit/testExample2()",
92
+
93
+ tests = []
94
+ data.each do |current_hash|
95
+ if current_hash["Subtests"]
96
+ tests += unfold_tests(current_hash["Subtests"])
97
+ end
98
+ if current_hash["TestStatus"]
99
+ tests << current_hash
100
+ end
101
+ end
102
+ return tests
103
+ end
104
+
105
+ # Convert the Hashes and Arrays in something more useful
106
+ def parse_content
107
+ self.data = self.raw_json["TestableSummaries"].collect do |testable_summary|
108
+ summary_row = {
109
+ project_path: testable_summary["ProjectPath"],
110
+ target_name: testable_summary["TargetName"],
111
+ test_name: testable_summary["TestName"],
112
+ duration: testable_summary["Tests"].map { |current_test| current_test["Duration"] }.inject(:+),
113
+ tests: unfold_tests(testable_summary["Tests"]).collect do |current_test|
114
+ current_row = {
115
+ identifier: current_test["TestIdentifier"],
116
+ test_group: current_test["TestIdentifier"].split("/")[0..-2].join("."),
117
+ name: current_test["TestName"],
118
+ object_class: current_test["TestObjectClass"],
119
+ status: current_test["TestStatus"],
120
+ guid: current_test["TestSummaryGUID"],
121
+ duration: current_test["Duration"]
122
+ }
123
+ if current_test["FailureSummaries"]
124
+ current_row[:failures] = current_test["FailureSummaries"].collect do |current_failure|
125
+ {
126
+ file_name: current_failure['FileName'],
127
+ line_number: current_failure['LineNumber'],
128
+ message: current_failure['Message'],
129
+ performance_failure: current_failure['PerformanceFailure'],
130
+ failure_message: "#{current_failure['Message']} (#{current_failure['FileName']}:#{current_failure['LineNumber']})"
131
+ }
132
+ end
133
+ end
134
+ current_row
135
+ end
136
+ }
137
+ summary_row[:number_of_tests] = summary_row[:tests].count
138
+ summary_row[:number_of_failures] = summary_row[:tests].find_all { |a| (a[:failures] || []).count > 0 }.count
139
+ summary_row
140
+ end
141
+ self.data.first[:run_destination_name] = self.raw_json["RunDestination"]["Name"]
142
+ return self.data
143
+ end
144
+ end
145
+ end
@@ -125,7 +125,7 @@ module Commander
125
125
  end
126
126
 
127
127
  def rescue_file_error(e)
128
- # We're also printing the new-lines, as otherwise the message is not very visible in-between the error and the stacktrace
128
+ # We're also printing the new-lines, as otherwise the message is not very visible in-between the error and the stack trace
129
129
  puts ""
130
130
  FastlaneCore::UI.important("Error accessing file, this might be due to fastlane's directory handling")
131
131
  FastlaneCore::UI.important("Check out https://docs.fastlane.tools/advanced/#directory-behavior for more details")
@@ -67,7 +67,14 @@ module Pilot
67
67
  # If no groups are passed to options, remove the tester from the app-level,
68
68
  # otherwise remove the tester from the groups specified.
69
69
  if config[:groups].nil? && tester.kind_of?(Spaceship::Tunes::Tester::External)
70
- test_flight_tester = Spaceship::TestFlight::Tester.find(app_id: app.apple_id, email: tester.email)
70
+ test_flight_testers = Spaceship::TestFlight::Tester.search(app_id: app.apple_id, text: tester.email, is_email_exact_match: true)
71
+
72
+ if test_flight_testers.length > 1
73
+ UI.user_error!("Could not remove #{tester.email} from app: #{app.name}, reason: too many matches: #{test_flight_testers}")
74
+ elsif test_flight_testers.length == 0
75
+ UI.user_error!("Could not remove #{tester.email} from app: #{app.name}, reason: unable to find tester on app")
76
+ end
77
+ test_flight_tester = test_flight_testers.first
71
78
  test_flight_tester.remove_from_app!(app_id: app.apple_id)
72
79
  UI.success("Successfully removed tester, #{test_flight_tester.email}, from app: #{app.name}")
73
80
  else
@@ -61,7 +61,9 @@ module Sigh
61
61
  # Fetches a profile matching the user's search requirements
62
62
  def fetch_profiles
63
63
  UI.message "Fetching profiles..."
64
- results = profile_type.find_by_bundle_id(Sigh.config[:app_identifier], mac: Sigh.config[:platform].to_s == 'macos')
64
+ results = profile_type.find_by_bundle_id(bundle_id: Sigh.config[:app_identifier],
65
+ mac: Sigh.config[:platform].to_s == 'macos',
66
+ sub_platform: Sigh.config[:platform].to_s == 'tvos' ? 'tvOS' : nil)
65
67
  results = results.find_all do |current_profile|
66
68
  if current_profile.valid? || Sigh.config[:force]
67
69
  true
@@ -72,28 +74,13 @@ module Sigh
72
74
  end
73
75
 
74
76
  # Take the provisioning profile name into account
75
- if Sigh.config[:provisioning_name].to_s.length > 0
76
- filtered = results.select { |p| p.name.strip == Sigh.config[:provisioning_name].strip }
77
- if Sigh.config[:ignore_profiles_with_different_name]
78
- results = filtered
79
- elsif (filtered || []).count > 0
80
- results = filtered
81
- end
82
- end
77
+ results = filter_profiles_by_name(results) if Sigh.config[:provisioning_name].to_s.length > 0
83
78
 
84
79
  # Since September 20, 2016 spaceship doesn't distinguish between AdHoc and AppStore profiles
85
80
  # any more, since it requires an additional request
86
81
  # Instead we only call is_adhoc? on the matching profiles to speed up spaceship
87
82
 
88
- results = results.find_all do |current_profile|
89
- if profile_type == Spaceship.provisioning_profile.ad_hoc
90
- current_profile.is_adhoc?
91
- elsif profile_type == Spaceship.provisioning_profile.app_store
92
- !current_profile.is_adhoc?
93
- else
94
- true
95
- end
96
- end
83
+ results = filter_profiles_for_adhoc_or_app_store(results)
97
84
 
98
85
  return results if Sigh.config[:skip_certificate_verification]
99
86
 
@@ -154,6 +141,28 @@ module Sigh
154
141
  profile
155
142
  end
156
143
 
144
+ def filter_profiles_by_name(profiles)
145
+ filtered = profiles.select { |p| p.name.strip == Sigh.config[:provisioning_name].strip }
146
+ if Sigh.config[:ignore_profiles_with_different_name]
147
+ profiles = filtered
148
+ elsif (filtered || []).count > 0
149
+ profiles = filtered
150
+ end
151
+ profiles
152
+ end
153
+
154
+ def filter_profiles_for_adhoc_or_app_store(profiles)
155
+ profiles.find_all do |current_profile|
156
+ if profile_type == Spaceship.provisioning_profile.ad_hoc
157
+ current_profile.is_adhoc?
158
+ elsif profile_type == Spaceship.provisioning_profile.app_store
159
+ !current_profile.is_adhoc?
160
+ else
161
+ true
162
+ end
163
+ end
164
+ end
165
+
157
166
  def certificates_for_profile_and_platform
158
167
  case Sigh.config[:platform].to_s
159
168
  when 'ios', 'tvos'
@@ -79,6 +79,7 @@ Get in contact with the developer on Twitter: [@FastlaneTools](https://twitter.c
79
79
 
80
80
  # Features
81
81
  - Create hundreds of screenshots in multiple languages on all simulators
82
+ - Take screenshots in multiple device simulators concurrently to cut down execution time (Xcode 9 only)
82
83
  - Configure it once, store the configuration in git
83
84
  - Do something else, while the computer takes the screenshots for you
84
85
  - Integrates with [`fastlane`](https://fastlane.tools) and [`deliver`](https://github.com/fastlane/fastlane/tree/master/deliver)
@@ -133,6 +134,7 @@ Here a few links to get started:
133
134
  - Create a new UI Test target in your Xcode project ([top part of this article](https://krausefx.com/blog/run-xcode-7-ui-tests-from-the-command-line))
134
135
  - Run `fastlane snapshot init` in your project folder
135
136
  - Add the ./SnapshotHelper.swift to your UI Test target (You can move the file anywhere you want)
137
+ - **Note:** if you're using Xcode 8, add the ./SnapshotHelperXcode8.swift to your UI Test target
136
138
  - (Objective C only) add the bridging header to your test class.
137
139
  - `#import "MYUITests-Swift.h"`
138
140
  - The bridging header is named after your test target with -Swift.h appended.
@@ -371,7 +373,7 @@ If you want to add frames around the screenshots and even put a title on top, ch
371
373
 
372
374
  ## Available language codes
373
375
  ```ruby
374
- ALL_LANGUAGES = ["da", "de-DE", "el", "en-AU", "en-CA", "en-GB", "en-US", "es-ES", "es-MX", "fi", "fr-CA", "fr-FR", "id", "it", "ja", "ko", "ms", "nl", "no", "pt-BR", "pt-PT", "ru", "sv", "th", "tr", "vi", "zh-Hans", "zh-Hant"]
376
+ ALL_LANGUAGES = ["da", "de-DE", "el", "en-AU", "en-CA", "en-GB", "en-US", "es-ES", "es-MX", "fi", "fr-CA", "fr-FR", "id", "it", "ja", "ko", "ms", "nl-NL", "no", "pt-BR", "pt-PT", "ru", "sv", "th", "tr", "vi", "zh-Hans", "zh-Hant"]
375
377
  ```
376
378
 
377
379
  To get more information about language and locale codes please read [Internationalization and Localization Guide](https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html).
@@ -7,7 +7,7 @@
7
7
  //
8
8
 
9
9
  // -----------------------------------------------------
10
- // IMPORTANT: When modifying this file, make sure to
10
+ // IMPORTANT: When modifying this file, make sure to
11
11
  // increment the version number at the very
12
12
  // bottom of the file to notify users about
13
13
  // the new SnapshotHelper.swift
@@ -32,20 +32,48 @@ func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) {
32
32
  Snapshot.snapshot(name, waitForLoadingIndicator: waitForLoadingIndicator)
33
33
  }
34
34
 
35
+ enum SnapshotError: Error, CustomDebugStringConvertible {
36
+ case cannotDetectUser
37
+ case cannotFindHomeDirectory
38
+ case cannotFindSimulatorHomeDirectory
39
+ case cannotAccessSimulatorHomeDirectory(String)
40
+
41
+ var debugDescription: String {
42
+ switch self {
43
+ case .cannotDetectUser:
44
+ return "Couldn't find Snapshot configuration files - can't detect current user "
45
+ case .cannotFindHomeDirectory:
46
+ return "Couldn't find Snapshot configuration files - can't detect `Users` dir"
47
+ case .cannotFindSimulatorHomeDirectory:
48
+ return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
49
+ case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome):
50
+ return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?"
51
+ }
52
+ }
53
+ }
54
+
35
55
  open class Snapshot: NSObject {
56
+ static var app: XCUIApplication!
57
+ static var cacheDirectory: URL!
58
+ static var screenshotsDirectory: URL? {
59
+ return cacheDirectory.appendingPathComponent("screenshots", isDirectory: true)
60
+ }
36
61
 
37
62
  open class func setupSnapshot(_ app: XCUIApplication) {
38
- setLanguage(app)
39
- setLocale(app)
40
- setLaunchArguments(app)
63
+ do {
64
+ let cacheDir = try pathPrefix()
65
+ Snapshot.cacheDirectory = cacheDir
66
+ Snapshot.app = app
67
+ setLanguage(app)
68
+ setLocale(app)
69
+ setLaunchArguments(app)
70
+ } catch let error {
71
+ print(error)
72
+ }
41
73
  }
42
74
 
43
75
  class func setLanguage(_ app: XCUIApplication) {
44
- guard let prefix = pathPrefix() else {
45
- return
46
- }
47
-
48
- let path = prefix.appendingPathComponent("language.txt")
76
+ let path = cacheDirectory.appendingPathComponent("language.txt")
49
77
 
50
78
  do {
51
79
  let trimCharacterSet = CharacterSet.whitespacesAndNewlines
@@ -57,11 +85,7 @@ open class Snapshot: NSObject {
57
85
  }
58
86
 
59
87
  class func setLocale(_ app: XCUIApplication) {
60
- guard let prefix = pathPrefix() else {
61
- return
62
- }
63
-
64
- let path = prefix.appendingPathComponent("locale.txt")
88
+ let path = cacheDirectory.appendingPathComponent("locale.txt")
65
89
 
66
90
  do {
67
91
  let trimCharacterSet = CharacterSet.whitespacesAndNewlines
@@ -76,11 +100,7 @@ open class Snapshot: NSObject {
76
100
  }
77
101
 
78
102
  class func setLaunchArguments(_ app: XCUIApplication) {
79
- guard let prefix = pathPrefix() else {
80
- return
81
- }
82
-
83
- let path = prefix.appendingPathComponent("snapshot-launch_arguments.txt")
103
+ let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
84
104
  app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]
85
105
 
86
106
  do {
@@ -105,12 +125,18 @@ open class Snapshot: NSObject {
105
125
 
106
126
  sleep(1) // Waiting for the animation to be finished (kind of)
107
127
 
108
- #if os(tvOS)
109
- XCUIApplication().childrenMatchingType(.Browser).count
110
- #elseif os(OSX)
128
+ #if os(OSX)
111
129
  XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
112
130
  #else
113
- XCUIDevice.shared().orientation = .unknown
131
+ let screenshot = app.windows.firstMatch.screenshot()
132
+ guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
133
+ let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
134
+ do {
135
+ try screenshot.pngRepresentation.write(to: path)
136
+ } catch let error {
137
+ print("Problem writing screenshot: \(name) to \(path)")
138
+ print(error)
139
+ }
114
140
  #endif
115
141
  }
116
142
 
@@ -127,30 +153,26 @@ open class Snapshot: NSObject {
127
153
  }
128
154
  }
129
155
 
130
- class func pathPrefix() -> URL? {
156
+ class func pathPrefix() throws -> URL? {
131
157
  let homeDir: URL
132
- //on OSX config is stored in /Users/<username>/Library
133
- //and on iOS/tvOS/WatchOS it's in simulator's home dir
158
+ // on OSX config is stored in /Users/<username>/Library
159
+ // and on iOS/tvOS/WatchOS it's in simulator's home dir
134
160
  #if os(OSX)
135
161
  guard let user = ProcessInfo().environment["USER"] else {
136
- print("Couldn't find Snapshot configuration files - can't detect current user ")
137
- return nil
162
+ throw SnapshotError.cannotDetectUser
138
163
  }
139
164
 
140
165
  guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
141
- print("Couldn't find Snapshot configuration files - can't detect `Users` dir")
142
- return nil
166
+ throw SnapshotError.cannotFindHomeDirectory
143
167
  }
144
168
 
145
169
  homeDir = usersDir.appendingPathComponent(user)
146
170
  #else
147
171
  guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
148
- print("Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable.")
149
- return nil
172
+ throw SnapshotError.cannotFindSimulatorHomeDirectory
150
173
  }
151
174
  guard let homeDirUrl = URL(string: simulatorHostHome) else {
152
- print("Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?")
153
- return nil
175
+ throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
154
176
  }
155
177
  homeDir = URL(fileURLWithPath: homeDirUrl.path)
156
178
  #endif
@@ -170,4 +192,4 @@ extension XCUIElement {
170
192
 
171
193
  // Please don't remove the lines below
172
194
  // They are used to detect outdated configuration files
173
- // SnapshotHelperVersion [1.4]
195
+ // SnapshotHelperVersion [1.5]