fastlane-plugin-snapshot_test 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a098870c72f522fd964d25677e67ea4261bc7dd9d2fe8cacaa129a82b2d98c21
4
+ data.tar.gz: a71eb32a3d7680fed8ec459704df16ecf41c7944106cf9a20eee9df513e16eae
5
+ SHA512:
6
+ metadata.gz: dadc72f1ac54582138ee9e54c40970ec727e0b99329e8f402e6c063cc5c559d12cbe8b7b904edb878e888e3cb3e5700677a0b9ab088788ca99cb63f49dda8a54
7
+ data.tar.gz: a1694820bd772d51179d2f61be4556a3bd67a2f0896f5c22d7808723efe8e8fc6700333566683627b79c0c3ed32c5fa5c8b9458588a3cb2800732d1c2223572f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Moyuru Aizawa <lvla0805@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # snapshot_test plugin
2
+
3
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-snapshot_test)
4
+
5
+ ## Getting Started
6
+
7
+ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-snapshot_test`, add it to your project by running:
8
+
9
+ ```bash
10
+ fastlane add_plugin snapshot_test
11
+ ```
12
+
13
+ This plugin depends Firebase Cloud Storage to store snapshots.
14
+ If you don't have a Firebase account, see [this](https://github.com/cats-oss/fastlane-plugin-firebase_test_lab_android#if-you-are-not-current-user-of-firebase) and create a bucket.
15
+
16
+ ## About snapshot_test
17
+ compare screenshots with previous commit's screenshots.
18
+
19
+ ## Example
20
+
21
+ Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
22
+
23
+ ## Run tests for this plugin
24
+
25
+ To run both the tests, and code style validation, run
26
+
27
+ ```
28
+ rake
29
+ ```
30
+
31
+ To automatically fix many of the styling issues, use
32
+ ```
33
+ rubocop -a
34
+ ```
35
+
36
+ ## Issues and Feedback
37
+
38
+ For any other issues and feedback about this plugin, please submit it to this repository.
39
+
40
+ ## Troubleshooting
41
+
42
+ If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
43
+
44
+ ## Using _fastlane_ Plugins
45
+
46
+ For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
47
+
48
+ ## About _fastlane_
49
+
50
+ _fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/snapshot_test/version'
2
+
3
+ module Fastlane
4
+ module SnapshotTest
5
+ # Return all .rb files inside the "actions" and "helper" directory
6
+ def self.all_classes
7
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ # By default we want to import all available actions and helpers
13
+ # A plugin can contain any number of actions and plugins
14
+ Fastlane::SnapshotTest.all_classes.each do |current|
15
+ require current
16
+ end
@@ -0,0 +1,61 @@
1
+ require_relative '../helper/helper'
2
+
3
+ module Fastlane
4
+ module Actions
5
+ class SaveSnapshotAction < Action
6
+ def self.run(params)
7
+ Helper.authenticate(params[:gcloud_service_key_file])
8
+
9
+ UI.message "Copy screenshots to working directory"
10
+ working_dir = params[:working_dir]
11
+ screenshot_dir = params[:screenshot_dir]
12
+ `mkdir #{working_dir}`
13
+ `rm -rf #{working_dir}/actual`
14
+ `mkdir #{working_dir}/actual`
15
+ Action.sh "cp -pR #{screenshot_dir}/* #{working_dir}/actual"
16
+ Action.sh "gsutil -m rsync -d -r #{working_dir} gs://#{params[:snapshot_bucket]}/#{Helper.get_current_commit_hash}"
17
+ end
18
+
19
+ def self.description
20
+ "Save Snapshot"
21
+ end
22
+
23
+ def self.details
24
+ "Save Snapshot"
25
+ end
26
+
27
+ def self.available_options
28
+ [
29
+ FastlaneCore::ConfigItem.new(key: :gcloud_service_key_file,
30
+ env_name: "GCLOUD_SERVICE_KEY_FILE",
31
+ description: "File path containing the gcloud auth key. Default: Created from GCLOUD_SERVICE_KEY environment variable",
32
+ type: String,
33
+ optional: false),
34
+ FastlaneCore::ConfigItem.new(key: :snapshot_bucket,
35
+ env_name: "SNAPSHOT_BUCKET",
36
+ description: "GCS Bucket that stores expected images",
37
+ type: String,
38
+ optional: false),
39
+ FastlaneCore::ConfigItem.new(key: :working_dir,
40
+ env_name: "WORKING_DIR",
41
+ description: "Working directory",
42
+ type: String,
43
+ optional: false),
44
+ FastlaneCore::ConfigItem.new(key: :screenshot_dir,
45
+ env_name: "SCREENSHOT_DIR",
46
+ description: "Working directory",
47
+ type: String,
48
+ optional: false)
49
+ ]
50
+ end
51
+
52
+ def self.authors
53
+ ["MoyuruAizawa"]
54
+ end
55
+
56
+ def self.is_supported?(platform)
57
+ true
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,119 @@
1
+ module Fastlane
2
+ module Actions
3
+ class ScreenshotAction < Action
4
+ def self.run(params)
5
+ download_dir = params[:download_dir]
6
+
7
+ firebase_test_lab_results_bucket = params[:firebase_test_lab_results_bucket] == nil ? "#{params[:project_id]}_test_results" : params[:firebase_test_lab_results_bucket]
8
+ firebase_test_lab_results_dir = "firebase_screenshot_#{DateTime.now.strftime('%Y-%m-%d-%H:%M:%S')}"
9
+ devices = params[:devices]
10
+ Fastlane::Actions::FirebaseTestLabAndroidAction.run(
11
+ project_id: params[:project_id],
12
+ gcloud_service_key_file: params[:gcloud_service_key_file],
13
+ type: "instrumentation",
14
+ devices: devices,
15
+ app_apk: params[:app_apk],
16
+ app_test_apk: params[:app_test_apk],
17
+ console_log_file_name: "#{download_dir}/firebase_os_test_console.log",
18
+ timeout: params[:timeout],
19
+ notify_to_slack: false,
20
+ extra_options: "--results-bucket #{firebase_test_lab_results_bucket} --results-dir #{firebase_test_lab_results_dir} --no-record-video"
21
+ )
22
+
23
+ UI.message "Fetch screenshots from Firebase Test Lab results bucket"
24
+ device_names = devices.map(&method(:device_name))
25
+ device_names.each do |device_name|
26
+ `mkdir -p #{download_dir}/#{device_name}`
27
+ Action.sh "gsutil -m rsync -d -r gs://#{firebase_test_lab_results_bucket}/#{firebase_test_lab_results_dir}/#{device_name}/artifacts #{download_dir}/#{device_name}"
28
+ `rm -rf #{download_dir}/#{device_name}/sdcard`
29
+ end
30
+ end
31
+
32
+ def self.device_name(device)
33
+ "#{device[:model]}-#{device[:version]}-#{device[:locale]}-#{device[:orientation]}"
34
+ end
35
+
36
+ def self.description
37
+ "Take screenshots"
38
+ end
39
+
40
+ def self.details
41
+ "Take screenshots"
42
+ end
43
+
44
+ def self.available_options
45
+ [
46
+ FastlaneCore::ConfigItem.new(key: :project_id,
47
+ env_name: "PROJECT_ID",
48
+ description: "Your Firebase project id",
49
+ type: String,
50
+ optional: false),
51
+ FastlaneCore::ConfigItem.new(key: :gcloud_service_key_file,
52
+ env_name: "GCLOUD_SERVICE_KEY_FILE",
53
+ description: "File path containing the gcloud auth key. Default: Created from GCLOUD_SERVICE_KEY environment variable",
54
+ type: String,
55
+ optional: false),
56
+ FastlaneCore::ConfigItem.new(key: :devices,
57
+ env_name: "DEVICES",
58
+ description: "Devices to test the app on",
59
+ type: Array,
60
+ verify_block: proc do |value|
61
+ UI.user_error!("Devices have to be at least one") if value.empty?
62
+ value.each do |device|
63
+ check_has_property(device, :model)
64
+ check_has_property(device, :version)
65
+ set_default_property(device, :locale, "en_US")
66
+ set_default_property(device, :orientation, "portrait")
67
+ end
68
+ end),
69
+ FastlaneCore::ConfigItem.new(key: :timeout,
70
+ env_name: "TIMEOUT",
71
+ description: "The max time this test execution can run before it is cancelled. Default: 5m (this value must be greater than or equal to 1m)",
72
+ type: String,
73
+ optional: true,
74
+ default_value: "5m"),
75
+ FastlaneCore::ConfigItem.new(key: :app_apk,
76
+ env_name: "APP_APK",
77
+ description: "The path for your android app apk",
78
+ type: String,
79
+ optional: false),
80
+ FastlaneCore::ConfigItem.new(key: :app_test_apk,
81
+ env_name: "APP_TEST_APK",
82
+ description: "The path for your android test apk. Default: empty string",
83
+ type: String,
84
+ optional: true,
85
+ default_value: nil),
86
+ FastlaneCore::ConfigItem.new(key: :firebase_test_lab_results_bucket,
87
+ env_name: "FIREBASE_TEST_LAB_RESULTS_BUCKET",
88
+ description: "Name of Firebase Test Lab results bucket",
89
+ type: String,
90
+ optional: true),
91
+ FastlaneCore::ConfigItem.new(key: :download_dir,
92
+ env_name: "DOWNLOAD_DIR",
93
+ description: "Target directory to download screenshots from firebase",
94
+ type: String,
95
+ optional: false)
96
+ ]
97
+ end
98
+
99
+ def self.authors
100
+ ["MoyuruAizawa"]
101
+ end
102
+
103
+ def self.is_supported?(platform)
104
+ platform == :android
105
+ end
106
+
107
+
108
+ def self.check_has_property(hash_obj, property)
109
+ UI.user_error!("Each device must have #{property} property") unless hash_obj.key?(property)
110
+ end
111
+
112
+ def self.set_default_property(hash_obj, property, default)
113
+ unless hash_obj.key?(property)
114
+ hash_obj[property] = default
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,224 @@
1
+ require_relative '../helper/github_notifier'
2
+ require_relative '../helper/helper'
3
+ require 'json'
4
+
5
+ module Fastlane
6
+ module Actions
7
+ class SnapshotTestAction < Action
8
+ def self.run(params)
9
+ Helper.authenticate(params[:gcloud_service_key_file])
10
+
11
+ UI.message "Copy screenshots to working directory"
12
+ working_dir = params[:working_dir]
13
+ screenshot_dir = params[:screenshot_dir]
14
+ `mkdir #{working_dir}`
15
+ `rm -rf #{working_dir}/actual`
16
+ `mkdir #{working_dir}/actual`
17
+ Action.sh "cp -pR #{screenshot_dir}/* #{working_dir}/actual"
18
+
19
+ UI.message "Fetch previous snapshot"
20
+ snapshot_bucket = params[:snapshot_bucket]
21
+ previous_commit = Helper.find_base_commit_hash(snapshot_bucket, params[:base_branch], Helper.get_current_branch)
22
+ if previous_commit == nil
23
+ UI.message "Previous snapshot not found"
24
+ return
25
+ end
26
+ UI.message "Previous Snapshot: #{previous_commit}"
27
+ UI.message "Fetch images from gs://#{snapshot_bucket}/#{previous_commit}"
28
+ `mkdir #{working_dir}/expected`
29
+ Action.sh "gsutil -m rsync -d -r gs://#{snapshot_bucket}/#{previous_commit}/actual #{working_dir}/expected"
30
+
31
+ UI.message "Compare snapshots"
32
+ `rm -rf #{working_dir}/diff`
33
+ `mkdir #{working_dir}/diff`
34
+ result = Comparator.compare_dir("#{working_dir}/expected", "#{working_dir}/actual", "#{working_dir}/diff", params[:fuzz])
35
+ open("#{working_dir}/result.json", "w") {|io| io.puts(JSON.pretty_generate(result))}
36
+ Action.sh "gsutil -m rsync -d -r #{working_dir} gs://#{snapshot_bucket}/#{Helper.get_current_commit_hash}"
37
+
38
+ UI.message result
39
+ UI.message "Notify to GitHub"
40
+ notify_github(params, result)
41
+ end
42
+
43
+ def self.notify_github(params, result)
44
+ return if params[:github_pr_number] == nil
45
+
46
+ bucket = params[:snapshot_bucket]
47
+ commit_hash = Helper.get_current_commit_hash
48
+
49
+ message = <<-EOS
50
+ ## Snapshot Test Result
51
+ Commit Hash: #{commit_hash}
52
+
53
+ #{summary_table(result[:new_items], result[:deleted_items], result[:changed_items], result[:passed_items])}
54
+
55
+ #{changed_items_table(result[:changed_items], bucket, commit_hash, params[:working_dir], params[:image_length])}
56
+
57
+ #{new_items_table(result[:new_items], bucket, commit_hash, params[:working_dir], params[:image_length])}
58
+
59
+ #{deleted_items_list(result[:deleted_items])}
60
+ EOS
61
+
62
+ GitHubNotifier.fold_comments(
63
+ params[:github_owner],
64
+ params[:github_repository],
65
+ params[:github_pr_number],
66
+ "## Snapshot Test Result",
67
+ "Open past snapshot test result",
68
+ params[:github_api_token]
69
+ )
70
+ GitHubNotifier.put_comment(
71
+ params[:github_owner],
72
+ params[:github_repository],
73
+ params[:github_pr_number],
74
+ message,
75
+ params[:github_api_token]
76
+ )
77
+ end
78
+
79
+ def self.summary_table(new_items, deleted_items, changed_items, passed_items)
80
+ <<-EOS
81
+ ### Summary
82
+ | | Count |
83
+ | --- | --- |
84
+ | New Screenshots | #{new_items.size} |
85
+ | Deleted Screenshots | #{deleted_items.size} |
86
+ | Changed Screenshots | #{changed_items.size} |
87
+ | Passed Screenshots | #{passed_items.size} |
88
+ EOS
89
+ end
90
+
91
+ def self.changed_items_table(changed_items, bucket, commit_hash, working_dir, image_height)
92
+ return "" if changed_items.empty?
93
+
94
+ header = "<tr><td></td><td>Before</td><td>After</td><td>Diff</td></tr>"
95
+ cells = changed_items.map {|item|
96
+ size_attr = generate_size_attr("#{working_dir}/actual/#{item}", image_height)
97
+
98
+ before = "<img src=\"#{object_url(bucket, commit_hash, item, "expected")}\" #{size_attr} />"
99
+ after = "<img src=\"#{object_url(bucket, commit_hash, item, "actual")}\" #{size_attr} />"
100
+ diff = "<img src=\"#{object_url(bucket, commit_hash, item, "diff")}\" #{size_attr} />"
101
+ "<tr><td>#{item}</td><td>#{before}</td><td>#{after}</td><td>#{diff}</td></tr>"
102
+ }.inject(&:+)
103
+
104
+ "### Changed Screenshots\n\n<table>#{header + cells}</table>"
105
+ end
106
+
107
+ def self.new_items_table(new_items, bucket, commit_hash, working_dir, image_height)
108
+ return "" if new_items.empty?
109
+
110
+ rows = new_items.each_slice(3).map {|oneline_items|
111
+ labels = oneline_items.map {|item| "<td>#{item}</td>"}.inject(&:+)
112
+ imgs = oneline_items.map {|item|
113
+ size_attr = generate_size_attr("#{working_dir}/actual/#{item}", image_height)
114
+ "<td><img src=\"#{object_url(bucket, commit_hash, item, "actual")}\" #{size_attr} /></td>"
115
+ }.inject(&:+)
116
+ "<tr>#{labels}</tr><tr>#{imgs}</tr>"
117
+ }.inject(&:+)
118
+
119
+ "### New Screenshots\n<details><summary>Open</summary>\n\n<table>#{rows}</table></details>\n"
120
+ end
121
+
122
+ def self.deleted_items_list(deleted_items)
123
+ return "" if deleted_items.empty?
124
+
125
+ "### Deleted Screenshots\n<details><summary>Open</summary>\n\n#{deleted_items.map {|item| "- #{item}\n"}.inject(&:+)}</details>\n"
126
+ end
127
+
128
+ def self.object_url(bucket, commit_hash, item_name, image_type)
129
+ path = "#{commit_hash}/#{image_type}/#{item_name}"
130
+ Helper.firebase_object_url(bucket, path)
131
+ end
132
+
133
+ def self.generate_size_attr(image_path, image_length)
134
+ return "" if image_length == nil
135
+
136
+ ratio = Helper.calc_aspect_ratio(image_path)
137
+ is_portrait = ratio >= 1.0
138
+ if is_portrait
139
+ height = image_length
140
+ width = height / ratio
141
+ "height=\"#{height}px\" width=\"#{width}px\""
142
+ else
143
+ width = image_length * ratio
144
+ height = width * ratio
145
+ "height=\"#{height}px\" width=\"#{width}px\""
146
+ end
147
+ end
148
+
149
+ def self.description
150
+ "Compare snapshots"
151
+ end
152
+
153
+ def self.authors
154
+ ["Moyuru Aizawa"]
155
+ end
156
+
157
+ def self.available_options
158
+ [
159
+ FastlaneCore::ConfigItem.new(key: :gcloud_service_key_file,
160
+ env_name: "GCLOUD_SERVICE_KEY_FILE",
161
+ description: "File path containing the gcloud auth key. Default: Created from GCLOUD_SERVICE_KEY environment variable",
162
+ type: String,
163
+ optional: false),
164
+ FastlaneCore::ConfigItem.new(key: :base_branch,
165
+ env_name: "BASE_BRANCH",
166
+ description: "Name of base branch",
167
+ type: String,
168
+ optional: true,
169
+ default_value: "master"),
170
+ FastlaneCore::ConfigItem.new(key: :snapshot_bucket,
171
+ env_name: "SNAPSHOT_BUCKET",
172
+ description: "GCS Bucket that stores expected images",
173
+ type: String,
174
+ optional: false),
175
+ FastlaneCore::ConfigItem.new(key: :fuzz,
176
+ env_name: "FUZZ",
177
+ description: "Colors within this distance are considered equal",
178
+ is_string: true,
179
+ optional: false,
180
+ default_value: "5%"),
181
+ FastlaneCore::ConfigItem.new(key: :working_dir,
182
+ env_name: "WORKING_DIR",
183
+ description: "Working directory",
184
+ type: String,
185
+ optional: false),
186
+ FastlaneCore::ConfigItem.new(key: :screenshot_dir,
187
+ env_name: "SCREENSHOT_DIR",
188
+ description: "Working directory",
189
+ type: String,
190
+ optional: false),
191
+ FastlaneCore::ConfigItem.new(key: :github_owner,
192
+ env_name: "GITHUB_OWNER",
193
+ description: "Owner name",
194
+ type: String,
195
+ optional: false),
196
+ FastlaneCore::ConfigItem.new(key: :github_repository,
197
+ env_name: "GITHUB_REPOSITORY",
198
+ description: "Repository name",
199
+ type: String,
200
+ optional: false),
201
+ FastlaneCore::ConfigItem.new(key: :github_pr_number,
202
+ env_name: "GITHUB_PR_NUMBER",
203
+ description: "Pull request number",
204
+ type: String,
205
+ optional: true),
206
+ FastlaneCore::ConfigItem.new(key: :github_api_token,
207
+ env_name: "GITHUB_API_TOKEN",
208
+ description: "GitHub API Token",
209
+ type: String,
210
+ optional: false),
211
+ FastlaneCore::ConfigItem.new(key: :image_length,
212
+ env_name: "IMAGE_LENGTH",
213
+ description: "Length px of the log side of screenshots",
214
+ is_string: false,
215
+ optional: true)
216
+ ]
217
+ end
218
+
219
+ def self.is_supported?(platform)
220
+ true
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,39 @@
1
+ require "json"
2
+
3
+ module Fastlane
4
+ UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
5
+
6
+ module Comparator
7
+ def self.compare(expected_file, actual_file, diff_path, fuzz)
8
+ UI.message "compare #{actual_file} and #{expected_file}"
9
+ system("compare -metric AE -fuzz #{fuzz} #{actual_file.shellescape} #{expected_file.shellescape} #{diff_path.shellescape}")
10
+ end
11
+
12
+ def self.compare_dir(expected_dir, actual_dir, diff_dir, fuzz)
13
+ UI.message "Compare #{expected_dir} and #{actual_dir}"
14
+ expect_items = Dir.glob("#{expected_dir}/*.jpg").map {|path| File.basename(path)}
15
+ actual_items = Dir.glob("#{actual_dir}/*.jpg").map {|path| File.basename(path)}
16
+
17
+ new_items = actual_items - expect_items
18
+ deleted_items = expect_items - actual_items
19
+ passed_items = []
20
+ changed_items = []
21
+
22
+ (actual_items & expect_items).each {|fileName|
23
+ is_passed = compare("#{actual_dir}/#{fileName}", "#{expected_dir}/#{fileName}", "#{diff_dir}/#{fileName}", fuzz)
24
+ if is_passed
25
+ passed_items << fileName
26
+ else
27
+ changed_items << fileName
28
+ end
29
+ }
30
+
31
+ {
32
+ :passed_items => passed_items,
33
+ :changed_items => changed_items,
34
+ :new_items => new_items,
35
+ :deleted_items => deleted_items
36
+ }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,87 @@
1
+ require 'fastlane_core/ui/ui'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ module Fastlane
7
+ module GitHubNotifier
8
+ def self.fold_comments(github_owner, github_repository, github_pr_number, comment_prefix, summary, github_api_token)
9
+ res = get_comments(github_owner, github_repository, github_pr_number, github_api_token)
10
+ JSON.parse(res.body)
11
+ .select {|comment| comment["body"].start_with?(comment_prefix)}
12
+ .each {|comment|
13
+ body = "<details><summary>#{summary}</summary>\n#{comment["body"]}\n\n</details>\n"
14
+ patch_comment(github_owner, github_repository, comment["id"], body, github_api_token)
15
+ }
16
+ end
17
+
18
+ def self.delete_comments(github_owner, github_repository, github_pr_number, comment_prefix, github_api_token)
19
+ res = get_comments(github_owner, github_repository, github_pr_number, github_api_token)
20
+ JSON.parse(res.body)
21
+ .select {|comment| comment["body"].start_with?(comment_prefix)}
22
+ .each {|comment| delete_comment(github_owner, github_repository, comment["id"], github_api_token)}
23
+ end
24
+
25
+ def self.get_comments(github_owner, github_repository, github_pr_number, github_api_token)
26
+ api_url = "https://api.github.com/repos/#{github_owner}/#{github_repository}/issues/#{github_pr_number}/comments"
27
+ UI.message "get comments #{api_url}"
28
+
29
+ uri = URI.parse(api_url)
30
+ req = Net::HTTP::Get.new(uri)
31
+ req["Content-Type"] = "application/json"
32
+ req["Authorization"] = "token #{github_api_token}"
33
+
34
+ res = Net::HTTP.start(uri.hostname, uri.port, {use_ssl: uri.scheme = "https"}) {|http| http.request(req)}
35
+ UI.message "#{res.code}\n#{res.body}"
36
+
37
+ res
38
+ end
39
+
40
+ def self.put_comment(github_owner, github_repository, github_pr_number, body, github_api_token)
41
+ api_url = "https://api.github.com/repos/#{github_owner}/#{github_repository}/issues/#{github_pr_number}/comments"
42
+ UI.message "put comment #{api_url}"
43
+
44
+ uri = URI.parse(api_url)
45
+ req = Net::HTTP::Post.new(uri)
46
+ req["Content-Type"] = "application/json"
47
+ req["Authorization"] = "token #{github_api_token}"
48
+ req.body = {:body => body}.to_json
49
+
50
+ res = Net::HTTP.start(uri.hostname, uri.port, {use_ssl: uri.scheme = "https"}) {|http| http.request(req)}
51
+ UI.message "#{res.code}\n#{res.body}"
52
+
53
+ res
54
+ end
55
+
56
+ def self.patch_comment(github_owner, github_repository, comment_id, body, github_api_token)
57
+ api_url = "https://api.github.com/repos/#{github_owner}/#{github_repository}/issues/comments/#{comment_id}"
58
+ UI.message "patch comment #{api_url}"
59
+
60
+ uri = URI.parse(api_url)
61
+ req = Net::HTTP::Patch.new(uri)
62
+ req["Content-Type"] = "application/json"
63
+ req["Authorization"] = "token #{github_api_token}"
64
+ req.body = {:body => body}.to_json
65
+
66
+ res = Net::HTTP.start(uri.hostname, uri.port, {use_ssl: uri.scheme = "https"}) {|http| http.request(req)}
67
+ UI.message "#{res.code}\n#{res.body}"
68
+
69
+ res
70
+ end
71
+
72
+ def self.delete_comment(github_owner, github_repository, comment_id, github_api_token)
73
+ api_url = "https://api.github.com/repos/#{github_owner}/#{github_repository}/issues/comments/#{comment_id}"
74
+ UI.message "delete comment #{api_url}"
75
+
76
+ uri = URI.parse(api_url)
77
+ req = Net::HTTP::Delete.new(uri)
78
+ req["Content-Type"] = "application/json"
79
+ req["Authorization"] = "token #{github_api_token}"
80
+
81
+ res = Net::HTTP.start(uri.hostname, uri.port, {use_ssl: uri.scheme = "https"}) {|http| http.request(req)}
82
+ UI.message "#{res.code}\n#{res.body}"
83
+
84
+ res
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,42 @@
1
+ require 'fastlane_core/ui/ui'
2
+
3
+ module Fastlane
4
+ UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
5
+
6
+ module Helper
7
+ def self.authenticate(gcloud_key_file)
8
+ UI.message "Authenticate with GCP"
9
+ Action.sh("gcloud auth activate-service-account --key-file #{gcloud_key_file}")
10
+ end
11
+
12
+ def self.get_current_commit_hash
13
+ Action.sh("git rev-parse HEAD").chomp!
14
+ end
15
+
16
+ def self.get_current_branch
17
+ Action.sh("git symbolic-ref --short HEAD").chomp!
18
+ end
19
+
20
+ def self.find_base_commit_hash(bucket_name, base_branch, current_branch)
21
+ base_commit_hash = Action.sh("git merge-base origin/#{base_branch} #{current_branch}").chomp!
22
+ dirs = `gsutil ls gs://#{bucket_name}/ | grep -e "/$"`.split("\n")
23
+ .map {|s| s[/(?<=gs:\/\/#{bucket_name}\/)(.*)(?=\/)/]}
24
+ hashes = Action.sh("git log origin/#{base_branch} --pretty=%H").split("\n")
25
+ hashes[hashes.index(base_commit_hash)..-1].each {|hash|
26
+ if dirs.include?(hash)
27
+ return hash
28
+ end
29
+ }
30
+ nil
31
+ end
32
+
33
+ def self.firebase_object_url(bucket, path)
34
+ "https://firebasestorage.googleapis.com/v0/b/#{bucket}/o/#{CGI.escape(path)}?alt=media"
35
+ end
36
+
37
+ def self.calc_aspect_ratio(imagePath)
38
+ width, height = FastImage.size(imagePath)
39
+ height / width.to_f
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module SnapshotTest
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-snapshot_test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Moyuru Aizawa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec_junit_formatter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.49.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.49.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-require_tools
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: fastlane
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 2.121.1
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 2.121.1
139
+ description:
140
+ email: lvla0805@gmail.com
141
+ executables: []
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - LICENSE
146
+ - README.md
147
+ - lib/fastlane/plugin/snapshot_test.rb
148
+ - lib/fastlane/plugin/snapshot_test/actions/save_snapshot_action.rb
149
+ - lib/fastlane/plugin/snapshot_test/actions/screenshot_action.rb
150
+ - lib/fastlane/plugin/snapshot_test/actions/snapshot_test_action.rb
151
+ - lib/fastlane/plugin/snapshot_test/helper/comparator.rb
152
+ - lib/fastlane/plugin/snapshot_test/helper/github_notifier.rb
153
+ - lib/fastlane/plugin/snapshot_test/helper/helper.rb
154
+ - lib/fastlane/plugin/snapshot_test/version.rb
155
+ homepage: https://github.com/cats-oss/fastlane-plugin-snapshot_test
156
+ licenses:
157
+ - MIT
158
+ metadata: {}
159
+ post_install_message:
160
+ rdoc_options: []
161
+ require_paths:
162
+ - lib
163
+ required_ruby_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ requirements: []
174
+ rubygems_version: 3.0.3
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: Compare snapshots
178
+ test_files: []