Skip to content

Commit

Permalink
Merge pull request #2 from qawolf/gen-190-update-fastlane-plugin-to-o…
Browse files Browse the repository at this point in the history
…ptionally-call-notify-deploy

add notify upload action
  • Loading branch information
smonn authored Oct 17, 2024
2 parents 9ecf170 + 1f3ec42 commit ecab691
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 29 deletions.
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To

```
# Add this to your Gemfile
gem "fastlane-plugin-qawolf", git: "https://github.com/qawolf/fastlane-plugin-qawolf", branch: "main"
gem "fastlane-plugin-qawolf", git: "https://github.com/qawolf/fastlane-plugin-qawolf", tag: "0.2.0"
```

## About qawolf

Fastlane plugin for QA Wolf integration.

Uploads build artifacts (IPA, APK, or AAB) to QA Wolf storage for automated testing.
Uploads build artifacts (IPA, APK, or AAB) to QA Wolf storage for automated testing. Optionally triggers a test run on QA Wolf.

> [!IMPORTANT]
> Testing iOS apps (IPA) on QA Wolf is not yet available.
Expand All @@ -24,17 +24,39 @@ Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plu

```ruby
lane :build do
# option 1: rely on env vars and output from other lanes
# QAWOLF_API_KEY must be set in the environment
# expects `gradle` to have set the APK (or AAB) output path
# Step 1: Build your app
# Ensure the APK/AAB file has been created. Your use case may vary.
gradle
qawolf # alias for upload_to_qawolf

# option 2: pass in the values directly
# Step 2: Upload the artifact to QA Wolf
upload_to_qawolf(
# Must be set or available as env var QAWOLF_API_KEY
qawolf_api_key: "qawolf_...",
file_path: "./build/app-bundle.apk",
filename: "app.apk"

# only set this if you have not built the artifact in the same lane
# e.g. via gradle or similar, check official Fastlane docs for details
file_path: "./build/app-bundle.apk"
)

# Step 3: Trigger a test run on QA Wolf
# optional, only use when deployment triggers are enabled in QA Wolf
notify_deploy_qawolf(
# Must be set or available as env var QAWOLF_API_KEY
qawolf_api_key: "qawolf_...",

# These fields are dependent on how triggers are setup within QA Wolf.
# Reach out to support for help. All fields are optional.
branch: nil,
commit_url: nil,
deduplication_key: nil,
deployment_type: nil,
deployment_url: nil,
hosting_service: nil,
sha: nil,
variables: nil,

# Only set this if your lane does not include `upload_to_qawolf`
run_input_path: nil,
)
end
```
Expand Down
15 changes: 8 additions & 7 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
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",
variables: {
HELLO: "WORLD"
}
)
# upload_to_qawolf(
# qawolf_api_key: ENV["QAWOLF_API_KEY"],
# file_path: "./fastlane/fastlane-test-app-debug.apk"
# )
end
144 changes: 144 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,144 @@
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]

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(params)
})
}

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

ENV["QAWOLF_RUN_ID"] = run_id

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.run_input_path(params)
if params[: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

return params[:run_input_path]
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
Loading

0 comments on commit ecab691

Please sign in to comment.