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
@@ -9,15 +9,21 @@ module Snapshot
9
9
  end
10
10
 
11
11
  File.write(snapfile_path, File.read("#{Snapshot::ROOT}/lib/assets/SnapfileTemplate"))
12
- File.write(File.join(path, 'SnapshotHelper.swift'), File.read("#{Snapshot::ROOT}/lib/assets/SnapshotHelper.swift"))
12
+ snapshot_helper_filename = "SnapshotHelperXcode8.swift"
13
+ if Helper.xcode_at_least?("9.0")
14
+ snapshot_helper_filename = "SnapshotHelper.swift"
15
+ end
16
+
17
+ # ensure that upgrade is cause when going from 8 to 9
18
+ File.write(File.join(path, snapshot_helper_filename), File.read("#{Snapshot::ROOT}/lib/assets/#{snapshot_helper_filename}"))
13
19
 
14
- puts "✅ Successfully created SnapshotHelper.swift '#{File.join(path, 'SnapshotHelper.swift')}'".green
20
+ puts "✅ Successfully created #{snapshot_helper_filename} '#{File.join(path, snapshot_helper_filename)}'".green
15
21
  puts "✅ Successfully created new Snapfile at '#{snapfile_path}'".green
16
22
 
17
23
  puts "-------------------------------------------------------".yellow
18
24
  puts "Open your Xcode project and make sure to do the following:".yellow
19
25
  puts "1) Add a new UI Test target to your project".yellow
20
- puts "2) Add the ./fastlane/SnapshotHelper.swift to your UI Test target".yellow
26
+ puts "2) Add the ./fastlane/#{snapshot_helper_filename} to your UI Test target".yellow
21
27
  puts " You can move the file anywhere you want".yellow
22
28
  puts "3) Call `setupSnapshot(app)` when launching your app".yellow
23
29
  puts ""
@@ -0,0 +1,53 @@
1
+
2
+ module Snapshot
3
+ class SimulatorLauncherConfiguration
4
+ # both
5
+ attr_accessor :languages
6
+ attr_accessor :devices
7
+ attr_accessor :add_photos
8
+ attr_accessor :add_videos
9
+ attr_accessor :clean
10
+ attr_accessor :erase_simulator
11
+ attr_accessor :localize_simulator
12
+ attr_accessor :reinstall_app
13
+ attr_accessor :app_identifier
14
+
15
+ # xcode 8
16
+ attr_accessor :number_of_retries
17
+ attr_accessor :stop_after_first_error
18
+ attr_accessor :output_simulator_logs
19
+
20
+ # runner
21
+ attr_accessor :launch_args_set
22
+ attr_accessor :output_directory
23
+
24
+ # xcode 9
25
+ attr_accessor :concurrent_simulators
26
+ alias concurrent_simulators? concurrent_simulators
27
+
28
+ def initialize(snapshot_config: nil)
29
+ @languages = snapshot_config[:languages]
30
+ @devices = snapshot_config[:devices]
31
+ @add_photos = snapshot_config[:add_photos]
32
+ @add_videos = snapshot_config[:add_videos]
33
+ @clean = snapshot_config[:clean]
34
+ @erase_simulator = snapshot_config[:erase_simulator]
35
+ @localize_simulator = snapshot_config[:localize_simulator]
36
+ @reinstall_app = snapshot_config[:reinstall_app]
37
+ @app_identifier = snapshot_config[:app_identifier]
38
+ @number_of_retries = snapshot_config[:number_of_retries]
39
+ @stop_after_first_error = snapshot_config[:stop_after_first_error]
40
+ @output_simulator_logs = snapshot_config[:output_simulator_logs]
41
+ @output_directory = snapshot_config[:output_directory]
42
+ @concurrent_simulators = snapshot_config[:concurrent_simulators]
43
+
44
+ launch_arguments = Array(snapshot_config[:launch_arguments])
45
+ # if more than 1 set of arguments, use a tuple with an index
46
+ if launch_arguments.count == 1
47
+ @launch_args_set = [launch_arguments]
48
+ else
49
+ @launch_args_set = launch_arguments.map.with_index { |e, i| [i, e] }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,203 @@
1
+ require 'snapshot/simulator_launchers/simulator_launcher_base'
2
+
3
+ module Snapshot
4
+ class SimulatorLauncher < SimulatorLauncherBase
5
+ # With Xcode 9's ability to run tests on multiple concurrent simulators,
6
+ # this method sets the maximum number of simulators to run simultaneously
7
+ # to avoid overloading your machine.
8
+ def default_number_of_simultaneous_simulators
9
+ cpu_count = CPUInspector.cpu_count
10
+ if cpu_count <= 2
11
+ return cpu_count
12
+ end
13
+
14
+ return cpu_count - 1
15
+ end
16
+
17
+ def take_screenshots_simultaneously
18
+ languages_finished = {}
19
+ launcher_config.launch_args_set.each do |launch_args|
20
+ launcher_config.languages.each_with_index do |language, language_index|
21
+ locale = nil
22
+ if language.kind_of?(Array)
23
+ locale = language[1]
24
+ language = language[0]
25
+ end
26
+
27
+ # Clear logs so subsequent xcodebuild executions dont append to old ones
28
+ log_path = xcodebuild_log_path(language: language, locale: locale)
29
+ File.delete(log_path) if File.exist?(log_path)
30
+
31
+ # Break up the array of devices into chunks that can
32
+ # be run simultaneously.
33
+ if launcher_config.concurrent_simulators?
34
+ device_batches = launcher_config.devices.each_slice(default_number_of_simultaneous_simulators).to_a
35
+ else
36
+ # Put each device in it's own array to run tests one at a time
37
+ device_batches = launcher_config.devices.map { |d| [d] }
38
+ end
39
+
40
+ device_batches.each do |devices|
41
+ languages_finished[language] = launch_simultaneously(devices, language, locale, launch_args)
42
+ end
43
+ end
44
+ end
45
+ launcher_config.devices.each_with_object({}) do |device, results_hash|
46
+ results_hash[device] = languages_finished
47
+ end
48
+ end
49
+
50
+ def launch_simultaneously(devices, language, locale, launch_arguments)
51
+ prepare_for_launch(language, locale, launch_arguments)
52
+
53
+ add_media(devices, :photo, launcher_config.add_photos) if launcher_config.add_photos
54
+ add_media(devices, :video, launcher_config.add_videos) if launcher_config.add_videos
55
+
56
+ command = TestCommandGenerator.generate(
57
+ devices: devices,
58
+ language: language,
59
+ locale: locale,
60
+ log_path: xcodebuild_log_path(language: language, locale: locale)
61
+ )
62
+
63
+ UI.important("Running snapshot on: #{devices.join(', ')}")
64
+
65
+ execute(command: command, language: language, locale: locale, launch_args: launch_arguments, devices: devices)
66
+
67
+ return copy_screenshots(language: language, locale: locale, launch_args: launch_arguments)
68
+ end
69
+
70
+ def execute(retries = 0, command: nil, language: nil, locale: nil, launch_args: nil, devices: nil)
71
+ prefix_hash = [
72
+ {
73
+ prefix: "Running Tests: ",
74
+ block: proc do |value|
75
+ value.include?("Touching")
76
+ end
77
+ }
78
+ ]
79
+ FastlaneCore::CommandExecutor.execute(command: command,
80
+ print_all: true,
81
+ print_command: true,
82
+ prefix: prefix_hash,
83
+ loading: "Loading...",
84
+ error: proc do |output, return_code|
85
+ self.collected_errors.concat(failed_devices.map do |device, messages|
86
+ "#{device}: #{messages.join(', ')}"
87
+ end)
88
+
89
+ cleanup_after_failure(devices, language, locale, launch_args, return_code)
90
+
91
+ # no exception raised... that means we need to retry
92
+ UI.error "Caught error... #{return_code}"
93
+
94
+ self.current_number_of_retries_due_to_failing_simulator += 1
95
+ if self.current_number_of_retries_due_to_failing_simulator < 20 && return_code != 65
96
+ # If the return code is not 65, we should assume its a simulator failure and retry
97
+ launch_simultaneously(devices, language, locale, launch_args)
98
+ elsif retries < launcher_config.number_of_retries
99
+ # If there are retries remaining, run the tests again
100
+ retry_tests(retries, command, language, locale, launch_args, devices)
101
+ else
102
+ # It's important to raise an error, as we don't want to collect the screenshots
103
+ UI.crash!("Too many errors... no more retries...") if launcher_config.stop_after_first_error
104
+ end
105
+ end)
106
+ end
107
+
108
+ def cleanup_after_failure(devices, language, locale, launch_args, return_code)
109
+ copy_screenshots(language: language, locale: locale, launch_args: launch_args)
110
+
111
+ UI.important("Tests failed while running on: #{devices.join(', ')}")
112
+ UI.important("For more detail about the test failures, check the logs here:")
113
+ UI.important(xcodebuild_log_path(language: language, locale: locale))
114
+ UI.important(" ")
115
+ UI.important("You can also find the test result data here:")
116
+ UI.important(test_results_path)
117
+ UI.important(" ")
118
+ UI.important("You can find the incomplete screenshots here:")
119
+ UI.important(SCREENSHOTS_DIR)
120
+ UI.important(launcher_config.output_directory)
121
+ end
122
+
123
+ def retry_tests(retries, command, language, locale, launch_args, devices)
124
+ UI.important("Retrying on devices: #{devices.join(', ')}")
125
+ UI.important("Number of retries remaining: #{launcher_config.number_of_retries - retries - 1}")
126
+ execute(retries + 1, command: command, language: language, locale: locale, launch_args: launch_args, devices: devices)
127
+ end
128
+
129
+ def copy_screenshots(language: nil, locale: nil, launch_args: nil)
130
+ raw_output = File.read(xcodebuild_log_path(language: language, locale: locale))
131
+ dir_name = locale || language
132
+ return Collector.fetch_screenshots(raw_output, dir_name, '', launch_args.first)
133
+ end
134
+
135
+ def test_results_path
136
+ derived_data_path = TestCommandGenerator.derived_data_path
137
+ return File.join(derived_data_path, 'Logs/Test')
138
+ end
139
+
140
+ # This method returns a hash of { device name => [failure messages] }
141
+ # {
142
+ # 'iPhone 7': [], # this empty array indicates success
143
+ # 'iPhone 7 Plus': ["No tests were executed"],
144
+ # 'iPad Air': ["Launch session expired", "Array out of bounds"]
145
+ # }
146
+ def failed_devices
147
+ test_summaries = Dir["#{test_results_path}/*_TestSummaries.plist"]
148
+ test_summaries.each_with_object({}) do |plist, hash|
149
+ summary = FastlaneCore::TestParser.new(plist)
150
+ name = summary.data.first[:run_destination_name]
151
+ if summary.data.first[:number_of_tests] == 0
152
+ hash[name] = ["No tests were executed"]
153
+ else
154
+ tests = Array(summary.data.first[:tests])
155
+ hash[name] = tests.map { |test| test[:failures].map { |failure| failure[:failure_message] } }.flatten
156
+ end
157
+ end
158
+ end
159
+
160
+ def xcodebuild_log_path(language: nil, locale: nil)
161
+ name_components = [Snapshot.project.app_name, Snapshot.config[:scheme]]
162
+
163
+ if Snapshot.config[:namespace_log_files]
164
+ name_components << launcher_config.devices.join('-') if launcher_config.devices.count >= 1
165
+ name_components << language if language
166
+ name_components << locale if locale
167
+ end
168
+
169
+ file_name = "#{name_components.join('-')}.log"
170
+
171
+ containing = File.expand_path(Snapshot.config[:buildlog_path])
172
+ FileUtils.mkdir_p(containing)
173
+
174
+ return File.join(containing, file_name)
175
+ end
176
+ end
177
+
178
+ class CPUInspector
179
+ def self.hwprefs_available?
180
+ `which hwprefs` != ''
181
+ end
182
+
183
+ def self.cpu_count
184
+ @cpu_count ||=
185
+ case RUBY_PLATFORM
186
+ when /darwin9/
187
+ `hwprefs cpu_count`.to_i
188
+ when /darwin10/
189
+ (hwprefs_available? ? `hwprefs thread_count` : `sysctl -n hw.physicalcpu_max`).to_i
190
+ when /linux/
191
+ UI.user_error!("We detected that you are running snapshot on Linux, but snapshot is only supported on macOS")
192
+ when /freebsd/
193
+ UI.user_error!("We detected that you are running snapshot on FreeBSD, but snapshot is only supported on macOS")
194
+ else
195
+ if RbConfig::CONFIG['host_os'] =~ /darwin/
196
+ (hwprefs_available? ? `hwprefs thread_count` : `sysctl -n hw.physicalcpu_max`).to_i
197
+ else
198
+ UI.crash!("Cannot find the machine's processor count.")
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,129 @@
1
+ module Snapshot
2
+ class SimulatorLauncherBase
3
+ attr_accessor :collected_errors
4
+
5
+ # The number of times we failed on launching the simulator... sigh
6
+ attr_accessor :current_number_of_retries_due_to_failing_simulator
7
+ attr_accessor :launcher_config
8
+
9
+ def initialize(launcher_configuration: nil)
10
+ @launcher_config = launcher_configuration
11
+ end
12
+
13
+ def collected_errors
14
+ @collected_errors ||= []
15
+ end
16
+
17
+ def current_number_of_retries_due_to_failing_simulator
18
+ @current_number_of_retries_due_to_failing_simulator || 0
19
+ end
20
+
21
+ def prepare_for_launch(language, locale, launch_arguments)
22
+ screenshots_path = TestCommandGenerator.derived_data_path
23
+ FileUtils.rm_rf(File.join(screenshots_path, "Logs"))
24
+ FileUtils.rm_rf(screenshots_path) if launcher_config.clean
25
+ FileUtils.mkdir_p(screenshots_path)
26
+
27
+ FileUtils.mkdir_p(CACHE_DIR)
28
+ FileUtils.mkdir_p(SCREENSHOTS_DIR)
29
+
30
+ File.write(File.join(CACHE_DIR, "language.txt"), language)
31
+ File.write(File.join(CACHE_DIR, "locale.txt"), locale || "")
32
+ File.write(File.join(CACHE_DIR, "snapshot-launch_arguments.txt"), launch_arguments.last)
33
+
34
+ prepare_simulators_for_launch(language: language, locale: locale)
35
+ end
36
+
37
+ def prepare_simulators_for_launch(language: nil, locale: nil)
38
+ # Kill and shutdown all currently running simulators so that the following settings
39
+ # changes will be picked up when they are started again.
40
+ Snapshot.kill_simulator # because of https://github.com/fastlane/snapshot/issues/337
41
+ `xcrun simctl shutdown booted &> /dev/null`
42
+
43
+ Fixes::SimulatorZoomFix.patch
44
+ Fixes::HardwareKeyboardFix.patch
45
+
46
+ devices = launcher_config.devices || []
47
+ devices.each do |type|
48
+ if launcher_config.erase_simulator || launcher_config.localize_simulator
49
+ erase_simulator(type)
50
+ if launcher_config.localize_simulator
51
+ localize_simulator(type, language, locale)
52
+ end
53
+ elsif launcher_config.reinstall_app
54
+ # no need to reinstall if device has been erased
55
+ uninstall_app(type)
56
+ end
57
+ end
58
+ end
59
+
60
+ # pass an array of device types
61
+ def add_media(device_types, media_type, paths)
62
+ media_type = media_type.to_s
63
+
64
+ device_types.each do |device_type|
65
+ UI.verbose "Adding #{media_type}s to #{device_type}..."
66
+ device_udid = TestCommandGenerator.device_udid(device_type)
67
+
68
+ UI.message "Launch Simulator #{device_type}"
69
+ Helper.backticks("xcrun instruments -w #{device_udid} &> /dev/null")
70
+
71
+ paths.each do |path|
72
+ UI.message "Adding '#{path}'"
73
+ Helper.backticks("xcrun simctl add#{media_type} #{device_udid} #{path.shellescape} &> /dev/null")
74
+ end
75
+ end
76
+ end
77
+
78
+ def uninstall_app(device_type)
79
+ UI.verbose "Uninstalling app '#{launcher_config.app_identifier}' from #{device_type}..."
80
+ launcher_config.app_identifier ||= UI.input("App Identifier: ")
81
+ device_udid = TestCommandGenerator.device_udid(device_type)
82
+
83
+ UI.message "Launch Simulator #{device_type}"
84
+ Helper.backticks("xcrun instruments -w #{device_udid} &> /dev/null")
85
+
86
+ UI.message "Uninstall application #{launcher_config.app_identifier}"
87
+ Helper.backticks("xcrun simctl uninstall #{device_udid} #{launcher_config.app_identifier} &> /dev/null")
88
+ end
89
+
90
+ def erase_simulator(device_type)
91
+ UI.verbose("Erasing #{device_type}...")
92
+ device_udid = TestCommandGenerator.device_udid(device_type)
93
+
94
+ UI.important("Erasing #{device_type}...")
95
+
96
+ `xcrun simctl erase #{device_udid} &> /dev/null`
97
+ end
98
+
99
+ def localize_simulator(device_type, language, locale)
100
+ device_udid = TestCommandGenerator.device_udid(device_type)
101
+ if device_udid
102
+ locale ||= language.sub("-", "_")
103
+ plist = {
104
+ AppleLocale: locale,
105
+ AppleLanguages: [language]
106
+ }
107
+ UI.message "Localizing #{device_type} (AppleLocale=#{locale} AppleLanguages=[#{language}])"
108
+ plist_path = "#{ENV['HOME']}/Library/Developer/CoreSimulator/Devices/#{device_udid}/data/Library/Preferences/.GlobalPreferences.plist"
109
+ File.write(plist_path, Plist::Emit.dump(plist))
110
+ end
111
+ end
112
+
113
+ def copy_simulator_logs(device_names, language, locale, launch_arguments)
114
+ return unless launcher_config.output_simulator_logs
115
+
116
+ detected_language = locale || language
117
+ language_folder = File.join(launcher_config.output_directory, detected_language)
118
+
119
+ device_names.each do |device_name|
120
+ device = TestCommandGeneratorBase.find_device(device_name)
121
+ components = [launch_arguments].delete_if { |a| a.to_s.length == 0 }
122
+
123
+ UI.header("Collecting system logs #{device_name} - #{language}")
124
+ log_identity = Digest::MD5.hexdigest(components.join("-"))
125
+ FastlaneCore::Simulator.copy_logs(device, log_identity, language_folder)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,110 @@
1
+ require 'snapshot/simulator_launchers/simulator_launcher_base'
2
+
3
+ module Snapshot
4
+ class SimulatorLauncherXcode8 < SimulatorLauncherBase
5
+ def take_screenshots_one_simulator_at_a_time
6
+ results = {} # collect all the results for a nice table
7
+ launcher_config.devices.each_with_index do |device, device_index|
8
+ # launch_args_set always has at at least 1 item (could be "")
9
+ launcher_config.launch_args_set.each do |launch_args|
10
+ launcher_config.languages.each_with_index do |language, language_index|
11
+ locale = nil
12
+ if language.kind_of?(Array)
13
+ locale = language[1]
14
+ language = language[0]
15
+ end
16
+ results[device] ||= {}
17
+
18
+ current_run = device_index * launcher_config.languages.count + language_index + 1
19
+ number_of_runs = launcher_config.languages.count * launcher_config.devices.count
20
+ UI.message("snapshot run #{current_run} of #{number_of_runs}")
21
+ results[device][language] = run_for_device_and_language(language, locale, device, launch_args)
22
+ copy_simulator_logs([device], language, locale, launch_args)
23
+ end
24
+ end
25
+ end
26
+ results
27
+ end
28
+
29
+ # This is its own method so that it can re-try if the tests fail randomly
30
+ # @return true/false depending on if the tests succeeded
31
+ def run_for_device_and_language(language, locale, device, launch_arguments, retries = 0)
32
+ return launch_one_at_a_time(language, locale, device, launch_arguments)
33
+ rescue => ex
34
+ UI.error ex.to_s # show the reason for failure to the user, but still maybe retry
35
+
36
+ if retries < launcher_config.number_of_retries
37
+ UI.important "Tests failed, re-trying #{retries + 1} out of #{launcher_config.number_of_retries + 1} times"
38
+ run_for_device_and_language(language, locale, device, launch_arguments, retries + 1)
39
+ else
40
+ UI.error "Backtrace:\n\t#{ex.backtrace.join("\n\t")}" if FastlaneCore::Globals.verbose?
41
+ self.collected_errors << ex
42
+ raise ex if launcher_config.stop_after_first_error
43
+ return false # for the results
44
+ end
45
+ end
46
+
47
+ # Returns true if it succeeded
48
+ def launch_one_at_a_time(language, locale, device_type, launch_arguments)
49
+ prepare_for_launch(language, locale, launch_arguments)
50
+
51
+ add_media([device_type], :photo, launcher_config.add_photos) if launcher_config.add_photos
52
+ add_media([device_type], :video, launcher_config.add_videos) if launcher_config.add_videos
53
+
54
+ open_simulator_for_device(device_type)
55
+
56
+ command = TestCommandGeneratorXcode8.generate(device_type: device_type, language: language, locale: locale)
57
+
58
+ if locale
59
+ UI.header("#{device_type} - #{language} (#{locale})")
60
+ else
61
+ UI.header("#{device_type} - #{language}")
62
+ end
63
+
64
+ execute(command: command, language: language, locale: locale, device_type: device_type, launch_args: launch_arguments)
65
+
66
+ raw_output = File.read(TestCommandGeneratorXcode8.xcodebuild_log_path(device_type: device_type, language: language, locale: locale))
67
+
68
+ dir_name = locale || language
69
+
70
+ return Collector.fetch_screenshots(raw_output, dir_name, device_type, launch_arguments.first)
71
+ end
72
+
73
+ def execute(command: nil, language: nil, locale: nil, device_type: nil, launch_args: nil)
74
+ prefix_hash = [
75
+ {
76
+ prefix: "Running Tests: ",
77
+ block: proc do |value|
78
+ value.include?("Touching")
79
+ end
80
+ }
81
+ ]
82
+ FastlaneCore::CommandExecutor.execute(command: command,
83
+ print_all: true,
84
+ print_command: true,
85
+ prefix: prefix_hash,
86
+ loading: "Loading...",
87
+ error: proc do |output, return_code|
88
+ ErrorHandler.handle_test_error(output, return_code)
89
+
90
+ # no exception raised... that means we need to retry
91
+ UI.error "Caught error... #{return_code}"
92
+
93
+ self.current_number_of_retries_due_to_failing_simulator += 1
94
+ if self.current_number_of_retries_due_to_failing_simulator < 20
95
+ launch_one_at_a_time(language, locale, device_type, launch_arguments)
96
+ else
97
+ # It's important to raise an error, as we don't want to collect the screenshots
98
+ UI.crash!("Too many errors... no more retries...")
99
+ end
100
+ end)
101
+ end
102
+
103
+ def open_simulator_for_device(device_name)
104
+ return unless FastlaneCore::Env.truthy?('FASTLANE_EXPLICIT_OPEN_SIMULATOR')
105
+
106
+ device = TestCommandGeneratorBase.find_device(device_name)
107
+ FastlaneCore::Simulator.launch(device) if device
108
+ end
109
+ end
110
+ end