fastlane 2.54.0.beta.20170822010003 → 2.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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]