Skip to content

Commit

Permalink
add notify upload action
Browse files Browse the repository at this point in the history
  • Loading branch information
smonn committed Oct 17, 2024
1 parent b0e0399 commit b637225
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 20 deletions.
17 changes: 10 additions & 7 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
lane :test do
qawolf(
file_path: "./fastlane/fastlane-test-app-debug.apk",
filename: "app.apk"
upload_to_qawolf(
file_path: "./fastlane/fastlane-test-app-debug.apk"
)
notify_deploy_qawolf(
deployment_type: "android",
run_input_path: "bad.apk",
variables: {
HELLO: "WORLD",
RUN_INPUT_PATH: "bad123.apk"
}
)
# upload_to_qawolf(
# qawolf_api_key: ENV["QAWOLF_API_KEY"],
# file_path: "./fastlane/fastlane-test-app-debug.apk"
# )
end
143 changes: 143 additions & 0 deletions lib/fastlane/plugin/qawolf/actions/notify_deploy_qawolf_action.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
require 'fastlane/action'
require 'fastlane_core'
require_relative '../helper/qawolf_helper'

module Fastlane
module Actions
module SharedValues
QAWOLF_RUN_ID = :QAWOLF_RUN_ID
end

# Casing is important for the action name!
class NotifyDeployQawolfAction < Action
def self.run(params)
qawolf_api_key = params[:qawolf_api_key] # Required
qawolf_base_url = params[:qawolf_base_url]
run_input_path = params[:run_input_path] || Actions.lane_context[SharedValues::QAWOLF_RUN_INPUT_PATH]

if run_input_path.nil?
UI.user_error!("🐺 No run input path found. Please run the `upload_to_qawolf` action first or set the `run_input_path` option.")
end

if params[:run_input_path].nil? == false && Actions.lane_context[SharedValues::QAWOLF_RUN_INPUT_PATH].nil? == false
UI.important("🐺 Both `run_input_path` and `QAWOLF_RUN_INPUT_PATH` are set. Using `run_input_path`.")
end

UI.message("🐺 Calling QA Wolf deploy success webhook...")

variables = params[:variables] || {}

options = {
branch: params[:branch],
commit_url: params[:commit_url],
deployment_type: params[:deployment_type],
deployment_url: params[:deployment_url],
deduplication_key: params[:deduplication_key],
hosting_service: params[:hosting_service],
sha: params[:sha],
variables: variables.merge({
RUN_INPUT_PATH: run_input_path
})
}

run_id = Helper::QawolfHelper.notify_deploy(qawolf_api_key, qawolf_base_url, options)

UI.success("🐺 QA Wolf triggered run: #{run_id}")
UI.success("🐺 Setting environment variable QAWOLF_RUN_ID = #{run_id}")

Actions.lane_context[SharedValues::QAWOLF_RUN_ID] = run_id
end

def self.description
"Fastlane plugin for QA Wolf integration to trigger test runs."
end

def self.authors
["QA Wolf"]
end

def self.details
"Calls the QA Wolf deployment success webhook to trigger test runs. Requires the `upload_to_qawolf` action to be run first."
end

def self.output
[
['QAWOLF_RUN_ID', 'The ID of the run triggered in QA Wolf.']
]
end

def self.available_options
[
FastlaneCore::ConfigItem.new(key: :qawolf_api_key,
env_name: "QAWOLF_API_KEY",
description: "Your QA Wolf API key",
optional: false,
type: String),
FastlaneCore::ConfigItem.new(key: :qawolf_base_url,
env_name: "QAWOLF_BASE_URL",
description: "Your QA Wolf base URL",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :branch,
description: "If using Git, set this to the branch name so it can be displayed in the QA Wolf UI and find any pull requests in the linked repo",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :commit_url,
description: "If you do not specify a hosting service, include this and the `sha` option to ensure the commit hash is a clickable link in QA Wolf",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :deduplication_key,
description: "By default, new runs will cancel ongoing runs if the `branch` and `environment` combination is matched, so setting this will instead cancel runs that have the same key",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :deployment_type,
description: "Arbitrary string to describe the deployment type. Configured in the QA Wolf UI when creating deployment triggers",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :deployment_url,
description: "When set, will be available as `process.env.URL` in tests",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :hosting_service,
description: "GitHub, GitLab, etc. Must be configured in QA Wolf",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :sha,
description: "If a Git commit triggered this, include the commit hash so that we can create commit checks if you also have a GitHub repo linked. Also displayed in the QA Wolf UI",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :variables,
description: "Optional key-value pairs to pass to the test run. These will be available as `process.env` in tests",
optional: true,
type: Object),
FastlaneCore::ConfigItem.new(key: :run_input_path,
env_name: "QAWOLF_RUN_INPUT_PATH",
description: "The path of the run input file to run in QA Wolf. Set by the `upload_to_qawolf` action",
optional: true,
type: String)
]
end

def self.is_supported?(platform)
# Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
# See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
[:ios, :android].include?(platform)
end

def self.example_code
[
'notify_deploy_qawolf',
'notify_deploy_qawolf(
qawolf_api_key: ENV["QAWOLF_API_KEY"],
branch: "<BRANCH_NAME>",
commit_url: "<URL>",
deployment_type: "<DEPLOYMENT_TYPE>",
deployment_url: "<URL>",
hosting_service: "GitHub|GitLab",
sha: "<SHA>"
)'
]
end
end
end
end
16 changes: 8 additions & 8 deletions lib/fastlane/plugin/qawolf/actions/upload_to_qawolf_action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
module Fastlane
module Actions
module SharedValues
QAWOLF_PLAYGROUND_FILE_LOCATION = :QAWOLF_PLAYGROUND_FILE_LOCATION
QAWOLF_RUN_INPUT_PATH = :QAWOLF_RUN_INPUT_PATH
end

# Casing is important for the action name!
Expand All @@ -22,14 +22,14 @@ def self.run(params)

UI.message("🐺 Uploading to QA Wolf...")

playground_file_location = Helper::QawolfHelper.upload_file(qawolf_api_key, qawolf_base_url, file_path, filename)
run_input_path = Helper::QawolfHelper.upload_file(qawolf_api_key, qawolf_base_url, file_path, filename)

ENV["QAWOLF_PLAYGROUND_FILE_LOCATION"] = playground_file_location
ENV["QAWOLF_RUN_INPUT_PATH"] = run_input_path

UI.success("🐺 Uploaded #{file_path} to QA Wolf successfully. Playground file location: #{playground_file_location}")
UI.success("🐺 Setting environment variable QAWOLF_PLAYGROUND_FILE_LOCATION = #{playground_file_location}")
UI.success("🐺 Uploaded #{file_path} to QA Wolf successfully. Run input path: #{run_input_path}")
UI.success("🐺 Setting environment variable QAWOLF_RUN_INPUT_PATH = #{run_input_path}")

Actions.lane_context[SharedValues::QAWOLF_PLAYGROUND_FILE_LOCATION] = playground_file_location
Actions.lane_context[SharedValues::QAWOLF_RUN_INPUT_PATH] = run_input_path
end

# Validate file_path.
Expand All @@ -45,7 +45,7 @@ def self.validate_file_path(file_path)
end

def self.description
"Fastlane plugin for QA Wolf integration."
"Fastlane plugin for QA Wolf integration to upload executable artifacts."
end

def self.authors
Expand All @@ -58,7 +58,7 @@ def self.details

def self.output
[
['QAWOLF_PLAYGROUND_FILE_LOCATION', 'Uploaded file location in playgrounds.']
['QAWOLF_RUN_INPUT_PATH', 'Uploaded file location for the executable artifact.']
]
end

Expand Down
66 changes: 63 additions & 3 deletions lib/fastlane/plugin/qawolf/helper/qawolf_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Helper
class QawolfHelper
BASE_URL = "https://app.qawolf.com"
SIGNED_URL_ENDPOINT = "/api/v0/run-inputs-executables-signed-urls"
WEBHOOK_DEPLOY_SUCCESS_ENDPOINT = "/api/webhooks/deploy_success"

def self.get_signed_url(qawolf_api_key, qawolf_base_url, filename)
headers = {
Expand All @@ -29,7 +30,7 @@ def self.get_signed_url(qawolf_api_key, qawolf_base_url, filename)
]
end

# Uploads file to BrowserStack
# Uploads file to QA Wolf
# Params :
# +qawolf_api_key+:: QA Wolf API key
# +qawolf_base_url+:: QA Wolf API base URL
Expand All @@ -43,11 +44,11 @@ def self.upload_file(qawolf_api_key, qawolf_base_url, file_path, filename = nil)
content_type: "application/octet-stream"
}

signed_url, playground_file_location = get_signed_url(qawolf_api_key, qawolf_base_url, filename || File.basename(file_path))
signed_url, run_input_path = get_signed_url(qawolf_api_key, qawolf_base_url, filename || File.basename(file_path))

RestClient.put(signed_url, file_content, headers)

return playground_file_location
return run_input_path
rescue RestClient::ExceptionWithResponse => e
begin
error_response = e.response.to_s
Expand All @@ -59,6 +60,65 @@ def self.upload_file(qawolf_api_key, qawolf_base_url, file_path, filename = nil)
rescue StandardError => e
UI.user_error!("App upload failed!!! Reason : #{e.message}")
end

def self.notify_deploy_body(options)
{
'branch' => options[:branch],
'commit_url' => options[:commit_url],
'deduplication_key' => options[:deduplication_key],
'deployment_type' => options[:deployment_type],
'deployment_url' => options[:deployment_url],
'hosting_service' => options[:hosting_service],
'sha' => options[:sha],
'variables' => options[:variables]
}.to_json
end

def self.process_notify_response(response)
response_json = JSON.parse(response.to_s)

results = response_json["results"]

failed_trigger = results.find { |result| result["failure_reason"].nil? == false }
success_trigger = results.find { |result| result["created_suite_id"].nil? == false }

if failed_trigger.nil? && success_trigger.nil?
raise "no matched trigger, reach out to QA Wolf support"
elsif failed_trigger.nil? == false
raise failed_trigger["failure_reason"]
end

return success_trigger["created_suite_id"]
end

# Triggers QA Wolf deploy success webhook to start test runs.
# Params :
# +qawolf_api_key+:: QA Wolf API key
# +qawolf_base_url+:: QA Wolf API base URL
# +options+:: Options hash containing deployment details.
def self.notify_deploy(qawolf_api_key, qawolf_base_url, options)
headers = {
authorization: "Bearer #{qawolf_api_key}",
user_agent: "qawolf_fastlane_plugin",
content_type: "application/json"
}

url = URI.join(qawolf_base_url || BASE_URL, WEBHOOK_DEPLOY_SUCCESS_ENDPOINT)

response = RestClient.post(url.to_s, notify_deploy_body(options), headers)

return process_notify_response(response)
rescue RestClient::ExceptionWithResponse => e
begin
error_response = e.response.to_s
rescue StandardError
error_response = "Internal server error"
end
# Give error if request failed.
UI.user_error!("Failed to notify deploy!!! Request failed. Reason : #{error_response}")
rescue StandardError => e
UI.user_error!("Failed to notify deploy!!! Something went wrong. Reason : #{e.message}")
end
end
end
end
2 changes: 1 addition & 1 deletion lib/fastlane/plugin/qawolf/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Fastlane
module Qawolf
VERSION = "0.1.0"
VERSION = "0.2.0"
end
end
72 changes: 72 additions & 0 deletions spec/fastlane/actions/notify_deploy_qawolf_action_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'webmock/rspec'

describe Fastlane::Actions::NotifyDeployQawolfAction do
describe "#run" do
let(:run_input_path) { "file.apk" }
let(:params) do
{
qawolf_api_key: "api_key",
run_input_path: run_input_path
}
end
let(:deploy_response) do
{
results: [{ created_suite_id: "created_suite_id" }]
}
end

before do
url = URI.join(Fastlane::Helper::QawolfHelper::BASE_URL, Fastlane::Helper::QawolfHelper::WEBHOOK_DEPLOY_SUCCESS_ENDPOINT)

stub_request(:post, url.to_s)
.to_return(
status: 200,
body: deploy_response.to_json,
headers: {}
)
end

it "triggers a test run" do
result = described_class.run(params)
expect(result).to eq(deploy_response[:results][0][:created_suite_id])
end

context "with no run input path set" do
let(:run_input_path) { nil }

it "fails when no test run is triggered" do
expect do
described_class.run(params)
end.to raise_error(FastlaneCore::Interface::FastlaneError)
end
end

context "with no results" do
let(:deploy_response) do
{
results: []
}
end

it "fails when no test run is triggered" do
expect do
described_class.run(params)
end.to raise_error(FastlaneCore::Interface::FastlaneError)
end
end

context "with failure reason set" do
let(:deploy_response) do
{
results: [{ failure_reason: "failure_reason" }]
}
end

it "fails when no test run is triggered" do
expect do
described_class.run(params)
end.to raise_error(FastlaneCore::Interface::FastlaneError)
end
end
end
end
2 changes: 1 addition & 1 deletion spec/fastlane/actions/upload_to_qawolf_action_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
let(:signed_url_response) do
{
signedUrl: "http://signed_url",
playgroundFileLocation: "playground_file_location"
playgroundFileLocation: "run_input_path"
}
end
let(:params) do
Expand Down

0 comments on commit b637225

Please sign in to comment.