fastlane 2.135.0 → 2.138.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -78
  3. data/fastlane/lib/fastlane/actions/app_store_build_number.rb +1 -1
  4. data/fastlane/lib/fastlane/actions/docs/capture_android_screenshots.md +38 -4
  5. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +19 -0
  6. data/fastlane/lib/fastlane/actions/docs/upload_to_play_store.md +4 -2
  7. data/fastlane/lib/fastlane/actions/ensure_bundle_exec.rb +3 -3
  8. data/fastlane/lib/fastlane/actions/get_version_number.rb +7 -2
  9. data/fastlane/lib/fastlane/actions/google_play_track_version_codes.rb +5 -1
  10. data/fastlane/lib/fastlane/actions/gradle.rb +11 -1
  11. data/fastlane/lib/fastlane/actions/increment_version_number.rb +6 -3
  12. data/fastlane/lib/fastlane/actions/latest_testflight_build_number.rb +2 -2
  13. data/fastlane/lib/fastlane/actions/register_devices.rb +5 -1
  14. data/fastlane/lib/fastlane/actions/ruby_version.rb +1 -1
  15. data/fastlane/lib/fastlane/actions/testfairy.rb +8 -1
  16. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -1
  17. data/fastlane/lib/fastlane/actions/xcode_select.rb +6 -1
  18. data/fastlane/lib/fastlane/cli_tools_distributor.rb +2 -2
  19. data/fastlane/lib/fastlane/commands_generator.rb +1 -1
  20. data/fastlane/lib/fastlane/helper/adb_helper.rb +13 -4
  21. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +1 -0
  22. data/fastlane/lib/fastlane/swift_fastlane_function.rb +9 -0
  23. data/fastlane/lib/fastlane/version.rb +1 -1
  24. data/fastlane/swift/Deliverfile.swift +1 -1
  25. data/fastlane/swift/Fastlane.swift +47 -17
  26. data/fastlane/swift/Gymfile.swift +1 -1
  27. data/fastlane/swift/Matchfile.swift +1 -1
  28. data/fastlane/swift/MatchfileProtocol.swift +6 -2
  29. data/fastlane/swift/Precheckfile.swift +1 -1
  30. data/fastlane/swift/Scanfile.swift +1 -1
  31. data/fastlane/swift/Screengrabfile.swift +1 -1
  32. data/fastlane/swift/ScreengrabfileProtocol.swift +14 -2
  33. data/fastlane/swift/Snapshotfile.swift +1 -1
  34. data/fastlane_core/lib/fastlane_core/helper.rb +1 -1
  35. data/match/lib/match/options.rb +8 -0
  36. data/match/lib/match/runner.rb +1 -0
  37. data/match/lib/match/storage/git_storage.rb +7 -2
  38. data/screengrab/lib/screengrab/module.rb +2 -0
  39. data/screengrab/lib/screengrab/options.rb +24 -11
  40. data/screengrab/lib/screengrab/runner.rb +79 -42
  41. data/spaceship/lib/spaceship/client.rb +9 -4
  42. data/spaceship/lib/spaceship/connect_api.rb +2 -0
  43. data/spaceship/lib/spaceship/connect_api/models/app.rb +11 -0
  44. data/spaceship/lib/spaceship/connect_api/models/beta_feedback.rb +71 -0
  45. data/spaceship/lib/spaceship/connect_api/models/beta_screenshot.rb +18 -0
  46. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +9 -0
  47. data/spaceship/lib/spaceship/tunes/tunes_client.rb +2 -5
  48. data/supply/lib/supply/client.rb +1 -0
  49. data/supply/lib/supply/options.rb +9 -2
  50. data/supply/lib/supply/uploader.rb +63 -39
  51. metadata +22 -24
  52. data/gym/lib/gym/.module.rb.swp +0 -0
  53. data/supply/lib/supply/.client.rb.swp +0 -0
  54. data/supply/lib/supply/.options.rb.swp +0 -0
  55. data/supply/lib/supply/.uploader.rb.swp +0 -0
@@ -18,4 +18,4 @@ class Gymfile: GymfileProtocol {
18
18
 
19
19
 
20
20
 
21
- // Generated with fastlane 2.135.0
21
+ // Generated with fastlane 2.138.0
@@ -18,4 +18,4 @@ class Matchfile: MatchfileProtocol {
18
18
 
19
19
 
20
20
 
21
- // Generated with fastlane 2.135.0
21
+ // Generated with fastlane 2.138.0
@@ -48,6 +48,9 @@ protocol MatchfileProtocol: class {
48
48
  /// Use a basic authorization header to access the git repo (e.g.: access via HTTPS, GitHub Actions, etc), usually a string in Base64
49
49
  var gitBasicAuthorization: String? { get }
50
50
 
51
+ /// Use a bearer authorization header to access the git repo (e.g.: access to an Azure Devops repository), usually a string in Base64
52
+ var gitBearerAuthorization: String? { get }
53
+
51
54
  /// Name of the Google Cloud Storage bucket to use
52
55
  var googleCloudBucketName: String? { get }
53
56
 
@@ -91,7 +94,7 @@ protocol MatchfileProtocol: class {
91
94
  extension MatchfileProtocol {
92
95
  var type: String { return "development" }
93
96
  var readonly: Bool { return false }
94
- var generateAppleCerts: Bool { return false }
97
+ var generateAppleCerts: Bool { return true }
95
98
  var skipProvisioningProfiles: Bool { return false }
96
99
  var appIdentifier: [String] { return [] }
97
100
  var username: String { return "" }
@@ -105,6 +108,7 @@ extension MatchfileProtocol {
105
108
  var shallowClone: Bool { return false }
106
109
  var cloneBranchDirectly: Bool { return false }
107
110
  var gitBasicAuthorization: String? { return nil }
111
+ var gitBearerAuthorization: String? { return nil }
108
112
  var googleCloudBucketName: String? { return nil }
109
113
  var googleCloudKeysFile: String? { return nil }
110
114
  var googleCloudProjectId: String? { return nil }
@@ -122,4 +126,4 @@ extension MatchfileProtocol {
122
126
 
123
127
  // Please don't remove the lines below
124
128
  // They are used to detect outdated files
125
- // FastlaneRunnerAPIVersion [0.9.10]
129
+ // FastlaneRunnerAPIVersion [0.9.12]
@@ -18,4 +18,4 @@ class Precheckfile: PrecheckfileProtocol {
18
18
 
19
19
 
20
20
 
21
- // Generated with fastlane 2.135.0
21
+ // Generated with fastlane 2.138.0
@@ -18,4 +18,4 @@ class Scanfile: ScanfileProtocol {
18
18
 
19
19
 
20
20
 
21
- // Generated with fastlane 2.135.0
21
+ // Generated with fastlane 2.138.0
@@ -18,4 +18,4 @@ class Screengrabfile: ScreengrabfileProtocol {
18
18
 
19
19
 
20
20
 
21
- // Generated with fastlane 2.135.0
21
+ // Generated with fastlane 2.138.0
@@ -39,6 +39,9 @@ protocol ScreengrabfileProtocol: class {
39
39
  /// Return the device to this locale after running tests
40
40
  var endingLocale: String { get }
41
41
 
42
+ /// Restarts the adb daemon using `adb root` to allow access to screenshots directories on device. Use if getting 'Permission denied' errors
43
+ var useAdbRoot: Bool { get }
44
+
42
45
  /// The path to the APK for the app under test
43
46
  var appApkPath: String? { get }
44
47
 
@@ -56,6 +59,12 @@ protocol ScreengrabfileProtocol: class {
56
59
 
57
60
  /// Enabling this option will automatically uninstall the application before running it
58
61
  var reinstallApp: Bool { get }
62
+
63
+ /// Add timestamp suffix to screenshot filename
64
+ var useTimestampSuffix: Bool { get }
65
+
66
+ /// Configure the host used by adb to connect, allows running on remote devices farm
67
+ var adbHost: String? { get }
59
68
  }
60
69
 
61
70
  extension ScreengrabfileProtocol {
@@ -70,16 +79,19 @@ extension ScreengrabfileProtocol {
70
79
  var useTestsInPackages: [String]? { return nil }
71
80
  var useTestsInClasses: [String]? { return nil }
72
81
  var launchArguments: [String]? { return nil }
73
- var testInstrumentationRunner: String { return "android.support.test.runner.AndroidJUnitRunner" }
82
+ var testInstrumentationRunner: String { return "androidx.test.runner.AndroidJUnitRunner" }
74
83
  var endingLocale: String { return "en-US" }
84
+ var useAdbRoot: Bool { return false }
75
85
  var appApkPath: String? { return nil }
76
86
  var testsApkPath: String? { return nil }
77
87
  var specificDevice: String? { return nil }
78
88
  var deviceType: String { return "phone" }
79
89
  var exitOnTestFailure: Bool { return true }
80
90
  var reinstallApp: Bool { return false }
91
+ var useTimestampSuffix: Bool { return true }
92
+ var adbHost: String? { return nil }
81
93
  }
82
94
 
83
95
  // Please don't remove the lines below
84
96
  // They are used to detect outdated files
85
- // FastlaneRunnerAPIVersion [0.9.11]
97
+ // FastlaneRunnerAPIVersion [0.9.13]
@@ -18,4 +18,4 @@ class Snapshotfile: SnapshotfileProtocol {
18
18
 
19
19
 
20
20
 
21
- // Generated with fastlane 2.135.0
21
+ // Generated with fastlane 2.138.0
@@ -72,7 +72,7 @@ module FastlaneCore
72
72
  # @return [boolean] true if building in a known CI environment
73
73
  def self.ci?
74
74
  # Check for Jenkins, Travis CI, ... environment variables
75
- ['JENKINS_HOME', 'JENKINS_URL', 'TRAVIS', 'CIRCLECI', 'CI', 'APPCENTER_BUILD_ID', 'TEAMCITY_VERSION', 'GO_PIPELINE_NAME', 'bamboo_buildKey', 'GITLAB_CI', 'XCS', 'TF_BUILD', 'GITHUB_ACTION', 'GITHUB_ACTIONS'].each do |current|
75
+ ['JENKINS_HOME', 'JENKINS_URL', 'TRAVIS', 'CIRCLECI', 'CI', 'APPCENTER_BUILD_ID', 'TEAMCITY_VERSION', 'GO_PIPELINE_NAME', 'bamboo_buildKey', 'GITLAB_CI', 'XCS', 'TF_BUILD', 'GITHUB_ACTION', 'GITHUB_ACTIONS', 'BITRISE_IO'].each do |current|
76
76
  return true if ENV.key?(current)
77
77
  end
78
78
  return false
@@ -125,6 +125,14 @@ module Match
125
125
  env_name: "MATCH_GIT_BASIC_AUTHORIZATION",
126
126
  sensitive: true,
127
127
  description: "Use a basic authorization header to access the git repo (e.g.: access via HTTPS, GitHub Actions, etc), usually a string in Base64",
128
+ conflicting_options: [:git_bearer_authorization],
129
+ optional: true,
130
+ default_value: nil),
131
+ FastlaneCore::ConfigItem.new(key: :git_bearer_authorization,
132
+ env_name: "MATCH_GIT_BEARER_AUTHORIZATION",
133
+ sensitive: true,
134
+ description: "Use a bearer authorization header to access the git repo (e.g.: access to an Azure Devops repository), usually a string in Base64",
135
+ conflicting_options: [:git_basic_authorization],
128
136
  optional: true,
129
137
  default_value: nil),
130
138
 
@@ -38,6 +38,7 @@ module Match
38
38
  git_user_email: params[:git_user_email],
39
39
  clone_branch_directly: params[:clone_branch_directly],
40
40
  git_basic_authorization: params[:git_basic_authorization],
41
+ git_bearer_authorization: params[:git_bearer_authorization],
41
42
  type: params[:type].to_s,
42
43
  generate_apple_certs: params[:generate_apple_certs],
43
44
  platform: params[:platform].to_s,
@@ -18,6 +18,7 @@ module Match
18
18
  attr_accessor :type
19
19
  attr_accessor :platform
20
20
  attr_accessor :git_basic_authorization
21
+ attr_accessor :git_bearer_authorization
21
22
 
22
23
  def self.configure(params)
23
24
  return self.new(
@@ -30,7 +31,8 @@ module Match
30
31
  git_full_name: params[:git_full_name],
31
32
  git_user_email: params[:git_user_email],
32
33
  clone_branch_directly: params[:clone_branch_directly],
33
- git_basic_authorization: params[:git_basic_authorization]
34
+ git_basic_authorization: params[:git_basic_authorization],
35
+ git_bearer_authorization: params[:git_bearer_authorization]
34
36
  )
35
37
  end
36
38
 
@@ -43,7 +45,8 @@ module Match
43
45
  git_full_name: nil,
44
46
  git_user_email: nil,
45
47
  clone_branch_directly: false,
46
- git_basic_authorization: nil)
48
+ git_basic_authorization: nil,
49
+ git_bearer_authorization: nil)
47
50
  self.git_url = git_url
48
51
  self.shallow_clone = shallow_clone
49
52
  self.skip_docs = skip_docs
@@ -52,6 +55,7 @@ module Match
52
55
  self.git_user_email = git_user_email
53
56
  self.clone_branch_directly = clone_branch_directly
54
57
  self.git_basic_authorization = git_basic_authorization
58
+ self.git_bearer_authorization = git_bearer_authorization
55
59
 
56
60
  self.type = type if type
57
61
  self.platform = platform if platform
@@ -70,6 +74,7 @@ module Match
70
74
 
71
75
  command = "git clone #{self.git_url.shellescape} #{self.working_directory.shellescape}"
72
76
  command << " -c http.extraheader='AUTHORIZATION: basic #{self.git_basic_authorization}'" unless self.git_basic_authorization.nil?
77
+ command << " -c http.extraheader='AUTHORIZATION: bearer #{self.git_bearer_authorization}'" unless self.git_bearer_authorization.nil?
73
78
 
74
79
  if self.shallow_clone
75
80
  command << " --depth 1 --no-single-branch"
@@ -1,4 +1,5 @@
1
1
  require 'fastlane_core/helper'
2
+ require 'fastlane/boolean'
2
3
  require_relative 'detect_values'
3
4
 
4
5
  module Screengrab
@@ -19,6 +20,7 @@ module Screengrab
19
20
 
20
21
  Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
21
22
  UI = FastlaneCore::UI
23
+ Boolean = Fastlane::Boolean
22
24
  ROOT = Pathname.new(File.expand_path('../../..', __FILE__))
23
25
  DESCRIPTION = "Automated localized screenshots of your Android app on every device".freeze
24
26
  end
@@ -32,7 +32,7 @@ module Screengrab
32
32
  env_name: 'SCREENGRAB_CLEAR_PREVIOUS_SCREENSHOTS',
33
33
  description: "Enabling this option will automatically clear previously generated screenshots before running screengrab",
34
34
  default_value: false,
35
- is_string: false),
35
+ type: Boolean),
36
36
  FastlaneCore::ConfigItem.new(key: :output_directory,
37
37
  short_option: "-o",
38
38
  env_name: "SCREENGRAB_OUTPUT_DIRECTORY",
@@ -43,7 +43,7 @@ module Screengrab
43
43
  description: "Don't open the summary after running _screengrab_",
44
44
  default_value: DEFAULT_SKIP_OPEN_SUMMARY,
45
45
  default_value_dynamic: true,
46
- is_string: false),
46
+ type: Boolean),
47
47
  FastlaneCore::ConfigItem.new(key: :app_package_name,
48
48
  env_name: 'SCREENGRAB_APP_PACKAGE_NAME',
49
49
  short_option: "-a",
@@ -68,22 +68,26 @@ module Screengrab
68
68
  type: Array,
69
69
  description: "Only run tests in these Java classes"),
70
70
  FastlaneCore::ConfigItem.new(key: :launch_arguments,
71
- env_name: 'SCREENGRAB_LAUNCH_ARGUMENTS',
72
- optional: true,
73
- short_option: "-e",
74
- type: Array,
75
- description: "Additional launch arguments"),
71
+ env_name: 'SCREENGRAB_LAUNCH_ARGUMENTS',
72
+ optional: true,
73
+ short_option: "-e",
74
+ type: Array,
75
+ description: "Additional launch arguments"),
76
76
  FastlaneCore::ConfigItem.new(key: :test_instrumentation_runner,
77
77
  env_name: 'SCREENGRAB_TEST_INSTRUMENTATION_RUNNER',
78
78
  optional: true,
79
- default_value: 'android.support.test.runner.AndroidJUnitRunner',
79
+ default_value: 'androidx.test.runner.AndroidJUnitRunner',
80
80
  description: "The fully qualified class name of your test instrumentation runner"),
81
81
  FastlaneCore::ConfigItem.new(key: :ending_locale,
82
82
  env_name: 'SCREENGRAB_ENDING_LOCALE',
83
83
  optional: true,
84
- is_string: true,
85
84
  default_value: 'en-US',
86
85
  description: "Return the device to this locale after running tests"),
86
+ FastlaneCore::ConfigItem.new(key: :use_adb_root,
87
+ env_name: 'SCREENGRAB_USE_ADB_ROOT',
88
+ description: "Restarts the adb daemon using `adb root` to allow access to screenshots directories on device. Use if getting 'Permission denied' errors",
89
+ default_value: false,
90
+ type: Boolean),
87
91
  FastlaneCore::ConfigItem.new(key: :app_apk_path,
88
92
  env_name: 'SCREENGRAB_APP_APK_PATH',
89
93
  optional: true,
@@ -123,12 +127,21 @@ module Screengrab
123
127
  env_name: 'EXIT_ON_TEST_FAILURE',
124
128
  description: "Whether or not to exit Screengrab on test failure. Exiting on failure will not copy sceenshots to local machine nor open sceenshots summary",
125
129
  default_value: true,
126
- is_string: false),
130
+ type: Boolean),
127
131
  FastlaneCore::ConfigItem.new(key: :reinstall_app,
128
132
  env_name: 'SCREENGRAB_REINSTALL_APP',
129
133
  description: "Enabling this option will automatically uninstall the application before running it",
130
134
  default_value: false,
131
- is_string: false)
135
+ type: Boolean),
136
+ FastlaneCore::ConfigItem.new(key: :use_timestamp_suffix,
137
+ env_name: 'SCREENGRAB_USE_TIMESTAMP_SUFFIX',
138
+ description: "Add timestamp suffix to screenshot filename",
139
+ default_value: true,
140
+ type: Boolean),
141
+ FastlaneCore::ConfigItem.new(key: :adb_host,
142
+ env_name: 'SCREENGRAB_ADB_HOST',
143
+ description: "Configure the host used by adb to connect, allows running on remote devices farm",
144
+ optional: true)
132
145
  ]
133
146
  end
134
147
  end
@@ -1,5 +1,6 @@
1
1
  require 'fastlane_core/print_table'
2
2
  require 'fastlane_core/command_executor'
3
+ require 'fastlane/helper/adb_helper'
3
4
  require_relative 'reports_generator'
4
5
  require_relative 'module'
5
6
 
@@ -57,8 +58,13 @@ module Screengrab
57
58
 
58
59
  device_screenshots_paths = [
59
60
  determine_external_screenshots_path(device_serial),
60
- determine_internal_screenshots_path
61
- ]
61
+ determine_internal_screenshots_paths(@config[:app_package_name], @config[:locales])
62
+ ].flatten
63
+
64
+ # Root is needed to access device paths at /data
65
+ if @config[:use_adb_root]
66
+ run_adb_command("root", print_all: false, print_command: true)
67
+ end
62
68
 
63
69
  clear_device_previous_screenshots(device_serial, device_screenshots_paths)
64
70
 
@@ -67,6 +73,8 @@ module Screengrab
67
73
 
68
74
  validate_apk(app_apk_path)
69
75
 
76
+ enable_clean_status_bar(device_serial, app_apk_path)
77
+
70
78
  run_tests(device_serial, app_apk_path, tests_apk_path, test_classes_to_use, test_packages_to_use, @config[:launch_arguments])
71
79
 
72
80
  number_of_screenshots = pull_screenshots_from_device(device_serial, device_screenshots_paths, device_type_dir_name)
@@ -77,35 +85,26 @@ module Screengrab
77
85
  end
78
86
 
79
87
  def select_device
80
- devices = run_adb_command("adb devices -l", print_all: true, print_command: true).split("\n")
81
- # the first output by adb devices is "List of devices attached" so remove that and any adb startup output
82
- devices.reject! do |device|
83
- [
84
- 'server is out of date', # The adb server is out of date and must be restarted
85
- 'unauthorized', # The device has not yet accepted ADB control
86
- 'offline', # The device is offline, skip it
87
- '* daemon', # Messages printed when the daemon is starting up
88
- 'List of devices attached', # Header of table for data we want
89
- "doesn't match this client" # Message printed when there is an ADB client/server version mismatch
90
- ].any? { |status| device.include?(status) }
91
- end
88
+ adb = Fastlane::Helper::AdbHelper.new(adb_host: @config[:adb_host])
89
+ devices = adb.load_all_devices
92
90
 
93
91
  UI.user_error!('There are no connected and authorized devices or emulators') if devices.empty?
94
92
 
95
- devices.select! { |d| d.include?(@config[:specific_device]) } if @config[:specific_device]
93
+ specific_device = @config[:specific_device]
94
+ if specific_device
95
+ devices.select! do |d|
96
+ d.serial.include?(specific_device)
97
+ end
98
+ end
96
99
 
97
- UI.user_error!("No connected devices matched your criteria: #{@config[:specific_device]}") if devices.empty?
100
+ UI.user_error!("No connected devices matched your criteria: #{specific_device}") if devices.empty?
98
101
 
99
102
  if devices.length > 1
100
103
  UI.important("Multiple connected devices, selecting the first one")
101
104
  UI.important("To specify which connected device to use, use the -s (specific_device) config option")
102
105
  end
103
106
 
104
- # grab the serial number. the lines of output can look like these:
105
- # 00c22d4d84aec525 device usb:2148663295X product:bullhead model:Nexus_5X device:bullhead
106
- # 192.168.1.100:5555 device usb:2148663295X product:bullhead model:Nexus_5X device:genymotion
107
- # emulator-5554 device usb:2148663295X product:bullhead model:Nexus_5X device:emulator
108
- devices[0].match(/^\S+/)[0]
107
+ devices.first.serial
109
108
  end
110
109
 
111
110
  def select_app_apk(discovered_apk_paths)
@@ -139,15 +138,19 @@ module Screengrab
139
138
  # macOS evaluates $foo in `echo $foo` before executing the command,
140
139
  # Windows doesn't - hence the double backslash vs. single backslash
141
140
  command = Helper.windows? ? "shell echo \$EXTERNAL_STORAGE " : "shell echo \\$EXTERNAL_STORAGE"
142
- device_ext_storage = run_adb_command("adb -s #{device_serial} #{command}",
141
+ device_ext_storage = run_adb_command("-s #{device_serial} #{command}",
143
142
  print_all: true,
144
143
  print_command: true)
145
144
  device_ext_storage = device_ext_storage.strip
146
145
  File.join(device_ext_storage, @config[:app_package_name], 'screengrab')
147
146
  end
148
147
 
149
- def determine_internal_screenshots_path
150
- "/data/data/#{@config[:app_package_name]}/app_screengrab"
148
+ def determine_internal_screenshots_paths(app_package_name, locales)
149
+ locale_paths = locales.map do |locale|
150
+ "/data/user/0/#{app_package_name}/files/#{app_package_name}/screengrab/#{locale}/images/screenshots"
151
+ end
152
+
153
+ return ["/data/data/#{app_package_name}/app_screengrab"] + locale_paths
151
154
  end
152
155
 
153
156
  def clear_device_previous_screenshots(device_serial, device_screenshots_paths)
@@ -155,7 +158,7 @@ module Screengrab
155
158
 
156
159
  device_screenshots_paths.each do |device_path|
157
160
  if_device_path_exists(device_serial, device_path) do |path|
158
- run_adb_command("adb -s #{device_serial} shell rm -rf #{path}",
161
+ run_adb_command("-s #{device_serial} shell rm -rf #{path}",
159
162
  print_all: true,
160
163
  print_command: true)
161
164
  end
@@ -182,13 +185,13 @@ module Screengrab
182
185
 
183
186
  def install_apks(device_serial, app_apk_path, tests_apk_path)
184
187
  UI.message('Installing app APK')
185
- apk_install_output = run_adb_command("adb -s #{device_serial} install -t -r #{app_apk_path.shellescape}",
188
+ apk_install_output = run_adb_command("-s #{device_serial} install -t -r #{app_apk_path.shellescape}",
186
189
  print_all: true,
187
190
  print_command: true)
188
191
  UI.user_error!("App APK could not be installed") if apk_install_output.include?("Failure [")
189
192
 
190
193
  UI.message('Installing tests APK')
191
- apk_install_output = run_adb_command("adb -s #{device_serial} install -t -r #{tests_apk_path.shellescape}",
194
+ apk_install_output = run_adb_command("-s #{device_serial} install -t -r #{tests_apk_path.shellescape}",
192
195
  print_all: true,
193
196
  print_command: true)
194
197
  UI.user_error!("Tests APK could not be installed") if apk_install_output.include?("Failure [")
@@ -199,14 +202,14 @@ module Screengrab
199
202
 
200
203
  if packages.include?(app_package_name.to_s)
201
204
  UI.message('Uninstalling app APK')
202
- run_adb_command("adb -s #{device_serial} uninstall #{app_package_name}",
205
+ run_adb_command("-s #{device_serial} uninstall #{app_package_name}",
203
206
  print_all: true,
204
207
  print_command: true)
205
208
  end
206
209
 
207
210
  if packages.include?(tests_package_name.to_s)
208
211
  UI.message('Uninstalling tests APK')
209
- run_adb_command("adb -s #{device_serial} uninstall #{tests_package_name}",
212
+ run_adb_command("-s #{device_serial} uninstall #{tests_package_name}",
210
213
  print_all: true,
211
214
  print_command: true)
212
215
  end
@@ -214,20 +217,16 @@ module Screengrab
214
217
 
215
218
  def grant_permissions(device_serial)
216
219
  UI.message('Granting the permission necessary to change locales on the device')
217
- run_adb_command("adb -s #{device_serial} shell pm grant #{@config[:app_package_name]} android.permission.CHANGE_CONFIGURATION",
220
+ run_adb_command("-s #{device_serial} shell pm grant #{@config[:app_package_name]} android.permission.CHANGE_CONFIGURATION",
218
221
  print_all: true,
219
222
  print_command: true)
220
223
 
221
- device_api_version = run_adb_command("adb -s #{device_serial} shell getprop ro.build.version.sdk",
222
- print_all: true,
223
- print_command: true).to_i
224
-
225
- if device_api_version >= 23
224
+ if device_api_version(device_serial) >= 23
226
225
  UI.message('Granting the permissions necessary to access device external storage')
227
- run_adb_command("adb -s #{device_serial} shell pm grant #{@config[:app_package_name]} android.permission.WRITE_EXTERNAL_STORAGE",
226
+ run_adb_command("-s #{device_serial} shell pm grant #{@config[:app_package_name]} android.permission.WRITE_EXTERNAL_STORAGE",
228
227
  print_all: true,
229
228
  print_command: true)
230
- run_adb_command("adb -s #{device_serial} shell pm grant #{@config[:app_package_name]} android.permission.READ_EXTERNAL_STORAGE",
229
+ run_adb_command("-s #{device_serial} shell pm grant #{@config[:app_package_name]} android.permission.READ_EXTERNAL_STORAGE",
231
230
  print_all: true,
232
231
  print_command: true)
233
232
  end
@@ -252,9 +251,10 @@ module Screengrab
252
251
  def run_tests_for_locale(locale, device_serial, test_classes_to_use, test_packages_to_use, launch_arguments)
253
252
  UI.message("Running tests for locale: #{locale}")
254
253
 
255
- instrument_command = ["adb -s #{device_serial} shell am instrument --no-window-animation -w",
254
+ instrument_command = ["-s #{device_serial} shell am instrument --no-window-animation -w",
256
255
  "-e testLocale #{locale.tr('-', '_')}",
257
256
  "-e endingLocale #{@config[:ending_locale].tr('-', '_')}"]
257
+ instrument_command << "-e appendTimestamp #{@config[:use_timestamp_suffix]}"
258
258
  instrument_command << "-e class #{test_classes_to_use.join(',')}" if test_classes_to_use
259
259
  instrument_command << "-e package #{test_packages_to_use.join(',')}" if test_packages_to_use
260
260
  instrument_command << launch_arguments.map { |item| '-e ' + item }.join(' ') if launch_arguments
@@ -281,10 +281,11 @@ module Screengrab
281
281
 
282
282
  # Make a temp directory into which to pull the screenshots before they are moved to their final location.
283
283
  # This makes directory cleanup easier, as the temp directory will be removed when the block completes.
284
+
284
285
  Dir.mktmpdir do |tempdir|
285
286
  device_screenshots_paths.each do |device_path|
286
287
  if_device_path_exists(device_serial, device_path) do |path|
287
- run_adb_command("adb -s #{device_serial} pull #{path} #{tempdir}",
288
+ run_adb_command("-s #{device_serial} pull #{path} #{tempdir}",
288
289
  print_all: false,
289
290
  print_command: true)
290
291
  end
@@ -347,7 +348,7 @@ module Screengrab
347
348
  # Some device commands fail if executed against a device path that does not exist, so this helper method
348
349
  # provides a way to conditionally execute a block only if the provided path exists on the device.
349
350
  def if_device_path_exists(device_serial, device_path)
350
- return if run_adb_command("adb -s #{device_serial} shell ls #{device_path}",
351
+ return if run_adb_command("-s #{device_serial} shell ls #{device_path}",
351
352
  print_all: false,
352
353
  print_command: false).include?('No such file')
353
354
 
@@ -359,7 +360,7 @@ module Screengrab
359
360
 
360
361
  # Return an array of packages that are installed on the device
361
362
  def installed_packages(device_serial)
362
- packages = run_adb_command("adb -s #{device_serial} shell pm list packages",
363
+ packages = run_adb_command("-s #{device_serial} shell pm list packages",
363
364
  print_all: true,
364
365
  print_command: true)
365
366
  packages.split("\n").map { |package| package.gsub("package:", "") }
@@ -367,7 +368,9 @@ module Screengrab
367
368
 
368
369
  def run_adb_command(command, print_all: false, print_command: false)
369
370
  adb_path = @android_env.adb_path.chomp("adb")
370
- output = @executor.execute(command: adb_path + command,
371
+ adb_host = @config[:adb_host]
372
+ host = adb_host.nil? ? '' : "-H #{adb_host} "
373
+ output = @executor.execute(command: adb_path + "adb " + host + command,
371
374
  print_all: print_all,
372
375
  print_command: print_command) || ''
373
376
  output.lines.reject do |line|
@@ -375,5 +378,39 @@ module Screengrab
375
378
  line.start_with?('adb: ')
376
379
  end.join('') # Lines retain their newline chars
377
380
  end
381
+
382
+ def device_api_version(device_serial)
383
+ run_adb_command("-s #{device_serial} shell getprop ro.build.version.sdk",
384
+ print_all: true, print_command: true).to_i
385
+ end
386
+
387
+ def enable_clean_status_bar(device_serial, app_apk_path)
388
+ return unless device_api_version(device_serial) >= 23
389
+
390
+ unless @android_env.aapt_path
391
+ UI.error("The `aapt` command could not be found, so status bar could not be cleaned. Make sure android_home is configured for screengrab or ANDROID_HOME is set in the environment")
392
+ return
393
+ end
394
+
395
+ # Check if the app wants to use the clean status bar feature
396
+ badging_dump = @executor.execute(command: "#{@android_env.aapt_path} dump badging #{app_apk_path}",
397
+ print_all: true, print_command: true)
398
+ return unless badging_dump.include?('uses-feature: name=\'tools.fastlane.screengrab.cleanstatusbar\'')
399
+
400
+ UI.message('Enabling clean status bar')
401
+
402
+ # Make sure the app requests the DUMP permission
403
+ unless badging_dump.include?('uses-permission: name=\'android.permission.DUMP\'')
404
+ UI.user_error!("The clean status bar feature requires the android.permission.DUMP permission but it could not be found in your app APK")
405
+ end
406
+
407
+ # Grant the DUMP permission
408
+ run_adb_command("-s #{device_serial} shell pm grant #{@config[:app_package_name]} android.permission.DUMP",
409
+ print_all: true, print_command: true)
410
+
411
+ # Enable the SystemUI demo mode
412
+ run_adb_command("-s #{device_serial} shell settings put global sysui_demo_allowed 1",
413
+ print_all: true, print_command: true)
414
+ end
378
415
  end
379
416
  end