Class: Snapshot::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/snapshot/runner.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#collected_errorsObject

All the errors we experience while running snapshot



10
11
12
# File 'lib/snapshot/runner.rb', line 10

def collected_errors
  @collected_errors
end

#number_of_retries_due_to_failing_simulatorObject

The number of times we failed on launching the simulator… sigh



7
8
9
# File 'lib/snapshot/runner.rb', line 7

def number_of_retries_due_to_failing_simulator
  @number_of_retries_due_to_failing_simulator
end

Instance Method Details

#add_media(device_type, media_type, paths) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/snapshot/runner.rb', line 243

def add_media(device_type, media_type, paths)
  media_type = media_type.to_s

  UI.verbose "Adding #{media_type}s to #{device_type}..."
  device_udid = TestCommandGenerator.device_udid(device_type)

  UI.message "Launch Simulator #{device_type}"
  Helper.backticks("xcrun instruments -w #{device_udid} &> /dev/null")

  paths.each do |path|
    UI.message "Adding '#{path}'"
    Helper.backticks("xcrun simctl add#{media_type} #{device_udid} #{path.shellescape} &> /dev/null")
  end
end

#clear_previous_screenshotsObject



258
259
260
261
262
263
264
265
# File 'lib/snapshot/runner.rb', line 258

def clear_previous_screenshots
  UI.important "Clearing previously generated screenshots"
  path = File.join(Snapshot.config[:output_directory], "*", "*.png")
  Dir[path].each do |current|
    UI.verbose "Deleting #{current}"
    File.delete(current)
  end
end

#config_launch_argumentsObject



89
90
91
92
93
94
95
96
97
# File 'lib/snapshot/runner.rb', line 89

def config_launch_arguments
  launch_arguments = Array(Snapshot.config[:launch_arguments])
  # if more than 1 set of arguments, use a tuple with an index
  if launch_arguments.count == 1
    [launch_arguments]
  else
    launch_arguments.map.with_index { |e, i| [i, e] }
  end
end

#erase_simulator(device_type) ⇒ Object



220
221
222
223
224
225
226
227
# File 'lib/snapshot/runner.rb', line 220

def erase_simulator(device_type)
  UI.verbose("Erasing #{device_type}...")
  device_udid = TestCommandGenerator.device_udid(device_type)

  UI.important("Erasing #{device_type}...")

  `xcrun simctl erase #{device_udid} &> /dev/null`
end

#launch(language, locale, device_type, launch_arguments) ⇒ Object

Returns true if it succeded



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
# File 'lib/snapshot/runner.rb', line 122

def launch(language, locale, device_type, launch_arguments)
  screenshots_path = TestCommandGenerator.derived_data_path
  FileUtils.rm_rf(File.join(screenshots_path, "Logs"))
  FileUtils.rm_rf(screenshots_path) if Snapshot.config[:clean]
  FileUtils.mkdir_p(screenshots_path)

  prefix = File.join(Dir.home, "Library/Caches/tools.fastlane")
  FileUtils.mkdir_p(prefix)
  File.write(File.join(prefix, "language.txt"), language)
  File.write(File.join(prefix, "locale.txt"), locale || "")
  File.write(File.join(prefix, "snapshot-launch_arguments.txt"), launch_arguments.last)

  # Kill and shutdown all currently running simulators so that the following settings
  # changes will be picked up when they are started again.
  Snapshot.kill_simulator # because of https://github.com/fastlane/snapshot/issues/337
  `xcrun simctl shutdown booted &> /dev/null`

  Fixes::SimulatorZoomFix.patch
  Fixes::HardwareKeyboardFix.patch

  if Snapshot.config[:erase_simulator] || Snapshot.config[:localize_simulator]
    erase_simulator(device_type)
    if Snapshot.config[:localize_simulator]
      localize_simulator(device_type, language, locale)
    end
  elsif Snapshot.config[:reinstall_app]
    # no need to reinstall if device has been erased
    uninstall_app(device_type)
  end

  add_media(device_type, :photo, Snapshot.config[:add_photos]) if Snapshot.config[:add_photos]
  add_media(device_type, :video, Snapshot.config[:add_videos]) if Snapshot.config[:add_videos]

  open_simulator_for_device(device_type)

  command = TestCommandGenerator.generate(device_type: device_type)

  if locale
    UI.header("#{device_type} - #{language} (#{locale})")
  else
    UI.header("#{device_type} - #{language}")
  end

  prefix_hash = [
    {
      prefix: "Running Tests: ",
      block: proc do |value|
        value.include?("Touching")
      end
    }
  ]

  FastlaneCore::CommandExecutor.execute(command: command,
                                      print_all: true,
                                  print_command: true,
                                         prefix: prefix_hash,
                                        loading: "Loading...",
                                          error: proc do |output, return_code|
                                            ErrorHandler.handle_test_error(output, return_code)

                                            # no exception raised... that means we need to retry
                                            UI.error "Caught error... #{return_code}"

                                            self.number_of_retries_due_to_failing_simulator += 1
                                            if self.number_of_retries_due_to_failing_simulator < 20
                                              launch(language, locale, device_type, launch_arguments)
                                            else
                                              # It's important to raise an error, as we don't want to collect the screenshots
                                              UI.crash!("Too many errors... no more retries...")
                                            end
                                          end)

  raw_output = File.read(TestCommandGenerator.xcodebuild_log_path)

  dir_name = locale || language

  return Collector.fetch_screenshots(raw_output, dir_name, device_type, launch_arguments.first)
end

#localize_simulator(device_type, language, locale) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/snapshot/runner.rb', line 229

def localize_simulator(device_type, language, locale)
  device_udid = TestCommandGenerator.device_udid(device_type)
  if device_udid
    locale ||= language.sub("-", "_")
    plist = {
      AppleLocale: locale,
      AppleLanguages: [language]
    }
    UI.message "Localizing #{device_type} (AppleLocale=#{locale} AppleLanguages=[#{language}])"
    plist_path = "#{ENV['HOME']}/Library/Developer/CoreSimulator/Devices/#{device_udid}/data/Library/Preferences/.GlobalPreferences.plist"
    File.write(plist_path, Plist::Emit.dump(plist))
  end
end

#open_simulator_for_device(device_name) ⇒ Object



201
202
203
204
205
206
# File 'lib/snapshot/runner.rb', line 201

def open_simulator_for_device(device_name)
  return unless ENV['FASTLANE_EXPLICIT_OPEN_SIMULATOR']

  device = TestCommandGenerator.find_device(device_name)
  FastlaneCore::Simulator.launch(device) if device
end


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/snapshot/runner.rb', line 99

def print_results(results)
  return if results.count == 0

  rows = []
  results.each do |device, languages|
    current = [device]
    languages.each do |language, value|
      current << (value == true ? " 💚" : "")
    end
    rows << current
  end

  params = {
    rows: rows,
    headings: ["Device"] + results.values.first.keys,
    title: "snapshot results"
  }
  puts ""
  puts Terminal::Table.new(params)
  puts ""
end

#run_for_device_and_language(language, locale, device, launch_arguments, retries = 0) ⇒ Object

This is its own method so that it can re-try if the tests fail randomly

Returns:

  • true/false depending on if the tests succeded



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/snapshot/runner.rb', line 73

def run_for_device_and_language(language, locale, device, launch_arguments, retries = 0)
  return launch(language, locale, device, launch_arguments)
rescue => ex
  UI.error ex.to_s # show the reason for failure to the user, but still maybe retry

  if retries < Snapshot.config[:number_of_retries]
    UI.important "Tests failed, re-trying #{retries + 1} out of #{Snapshot.config[:number_of_retries] + 1} times"
    run_for_device_and_language(language, locale, device, launch_arguments, retries + 1)
  else
    UI.error "Backtrace:\n\t#{ex.backtrace.join("\n\t")}" if $verbose
    self.collected_errors << ex
    raise ex if Snapshot.config[:stop_after_first_error]
    return false # for the results
  end
end

#uninstall_app(device_type) ⇒ Object



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/snapshot/runner.rb', line 208

def uninstall_app(device_type)
  UI.verbose "Uninstalling app '#{Snapshot.config[:app_identifier]}' from #{device_type}..."
  Snapshot.config[:app_identifier] ||= UI.input("App Identifier: ")
  device_udid = TestCommandGenerator.device_udid(device_type)

  UI.message "Launch Simulator #{device_type}"
  Helper.backticks("xcrun instruments -w #{device_udid} &> /dev/null")

  UI.message "Uninstall application #{Snapshot.config[:app_identifier]}"
  Helper.backticks("xcrun simctl uninstall #{device_udid} #{Snapshot.config[:app_identifier]} &> /dev/null")
end

#verify_helper_is_currentObject

rubocop:disable Style/Next



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/snapshot/runner.rb', line 277

def verify_helper_is_current
  current_version = version_of_bundled_helper
  UI.verbose "Checking that helper files contain #{current_version}"

  helper_files = Update.find_helper
  helper_files.each do |path|
    content = File.read(path)

    unless content.include?(current_version)
      UI.error "Your '#{path}' is outdated, please run `snapshot update`"
      UI.error "to update your Helper file"
      UI.user_error!("Please update your Snapshot Helper file")
    end
  end
end

#version_of_bundled_helperObject



267
268
269
270
271
272
273
274
# File 'lib/snapshot/runner.rb', line 267

def version_of_bundled_helper
  runner_dir = File.dirname(__FILE__)
  bundled_helper = File.read File.expand_path('../assets/SnapshotHelper.swift', runner_dir)
  current_version = bundled_helper.match(/\n.*SnapshotHelperVersion \[.+\]/)[0]

  ## Something like "// SnapshotHelperVersion [1.2]", but be relaxed about whitespace
  current_version.gsub(%r{^//\w*}, '').strip
end

#workObject



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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/snapshot/runner.rb', line 12

def work
  if File.exist?("./fastlane/snapshot.js") or File.exist?("./snapshot.js")
    UI.error "Found old snapshot configuration file 'snapshot.js'"
    UI.error "You updated to snapshot 1.0 which now uses UI Automation"
    UI.error "Please follow the migration guide: https://github.com/fastlane/fastlane/blob/master/snapshot/MigrationGuide.md"
    UI.error "And read the updated documentation: https://github.com/fastlane/fastlane/tree/master/snapshot"
    sleep 3 # to be sure the user sees this, as compiling clears the screen
  end

  Snapshot.config[:output_directory] = File.expand_path(Snapshot.config[:output_directory])

  verify_helper_is_current

  # Also print out the path to the used Xcode installation
  # We go 2 folders up, to not show "Contents/Developer/"
  values = Snapshot.config.values(ask: false)
  values[:xcode_path] = File.expand_path("../..", FastlaneCore::Helper.xcode_path)
  FastlaneCore::PrintTable.print_values(config: values, hide_keys: [], title: "Summary for snapshot #{Snapshot::VERSION}")

  clear_previous_screenshots if Snapshot.config[:clear_previous_screenshots]

  UI.success "Building and running project - this might take some time..."

  self.number_of_retries_due_to_failing_simulator = 0
  self.collected_errors = []
  results = {} # collect all the results for a nice table
  launch_arguments_set = config_launch_arguments
  Snapshot.config[:devices].each_with_index do |device, device_index|
    launch_arguments_set.each do |launch_arguments|
      Snapshot.config[:languages].each_with_index do |language, language_index|
        locale = nil
        if language.kind_of?(Array)
          locale = language[1]
          language = language[0]
        end
        results[device] ||= {}

        current_run = device_index * Snapshot.config[:languages].count + language_index + 1
        number_of_runs = Snapshot.config[:languages].count * Snapshot.config[:devices].count
        UI.message("snapshot run #{current_run} of #{number_of_runs}")

        results[device][language] = run_for_device_and_language(language, locale, device, launch_arguments)
      end
    end
  end

  print_results(results)

  UI.user_error!(self.collected_errors.join('; ')) if self.collected_errors.count > 0

  # Generate HTML report
  ReportsGenerator.new.generate

  # Clear the Derived Data
  unless Snapshot.config[:derived_data_path]
    FileUtils.rm_rf(TestCommandGenerator.derived_data_path)
  end
end