fastlane 2.135.0 → 2.138.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 (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