Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,42 @@ upload_to_browserstack_app_live(
```
Check out the example [Fastfile](https://github.com/browserstack/browserstack-fastlane-plugin/blob/master/fastlane/Fastfile) to see how to use this plugin. Try it by including that in your project's Fastfile, running `fastlane install_plugins` and `bundle exec fastlane test`. Please refer to this [sample android project](https://github.com/browserstack/browserstack-android-sample-app), which demonstrates the use of this plugin.

Once the app upload is successful, the app id of the app will be stored in an environment variable, "BROWSERSTACK_APP_ID" and it can be accessed in your tests in the following way :
### Environment variable fallbacks (dotenv friendly)

All action parameters support environment variable fallback.

For `upload_to_browserstack_app_automate`:

- `browserstack_username` -> `BROWSERSTACK_USERNAME`
- `browserstack_access_key` -> `BROWSERSTACK_ACCESS_KEY`
- `file_path` -> `BROWSERSTACK_FILE_PATH`
- `custom_id` -> `BROWSERSTACK_CUSTOM_ID`
- `ios_keychain_support` -> `BROWSERSTACK_IOS_KEYCHAIN_SUPPORT`

For `upload_to_browserstack_app_live`:

- `browserstack_username` -> `BROWSERSTACK_USERNAME`
- `browserstack_access_key` -> `BROWSERSTACK_ACCESS_KEY`
- `file_path` -> `BROWSERSTACK_FILE_PATH`

### Upload outputs

For `upload_to_browserstack_app_automate`:

- `BROWSERSTACK_APP_ID` always contains `app_url` from BrowserStack upload response.
- `BROWSERSTACK_CUSTOM_ID` contains `custom_id` from BrowserStack upload response.
- If `custom_id` is absent in response, `BROWSERSTACK_CUSTOM_ID` is set to empty string (`""`).

The same values are also exposed in lane context:

- `SharedValues::BROWSERSTACK_APP_ID`
- `SharedValues::BROWSERSTACK_CUSTOM_ID`

For `upload_to_browserstack_app_live`:

- `BROWSERSTACK_LIVE_APP_ID` contains `app_url` from BrowserStack upload response.

Once the app upload is successful, the app URL of the app will be stored in an environment variable, "BROWSERSTACK_APP_ID" and it can be accessed in your tests in the following way :
```
String app = System.getenv("BROWSERSTACK_APP_ID"); // Get app id from environment variable.
capabilities.setCapability("app", app); // Add app id to driver capability.
Expand Down Expand Up @@ -82,4 +117,3 @@ For more information about how the `fastlane` plugin system works, check out the
## About _fastlane_

_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).

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Fastlane
module Actions
module SharedValues
BROWSERSTACK_APP_ID ||= :BROWSERSTACK_APP_ID
BROWSERSTACK_CUSTOM_ID ||= :BROWSERSTACK_CUSTOM_ID
end
class UploadToBrowserstackAppAutomateAction < Action
SUPPORTED_FILE_EXTENSIONS = ["apk", "ipa", "aab"]
Expand All @@ -23,17 +24,22 @@ def self.run(params)

UI.message("Uploading app to BrowserStack AppAutomate...")

browserstack_app_id = Helper::BrowserstackHelper.upload_file(browserstack_username, browserstack_access_key, file_path, UPLOAD_API_ENDPOINT, custom_id, ios_keychain_support)
upload_response = Helper::BrowserstackHelper.upload_file(browserstack_username, browserstack_access_key, file_path, UPLOAD_API_ENDPOINT, custom_id, ios_keychain_support)
browserstack_app_id = upload_response["app_url"]
browserstack_custom_id = upload_response["custom_id"] || ""

# Set 'BROWSERSTACK_APP_ID' environment variable, if app upload was successful.
ENV['BROWSERSTACK_APP_ID'] = browserstack_app_id
ENV['BROWSERSTACK_CUSTOM_ID'] = browserstack_custom_id

UI.success("Successfully uploaded app " + file_path + " to BrowserStack AppAutomate with app_url : " + browserstack_app_id)

UI.success("Setting Environment variable BROWSERSTACK_APP_ID = " + browserstack_app_id)
UI.success("Setting Environment variable BROWSERSTACK_CUSTOM_ID = " + browserstack_custom_id)

# Setting app id in SharedValues, which can be used by other fastlane actions.
Actions.lane_context[SharedValues::BROWSERSTACK_APP_ID] = browserstack_app_id
Actions.lane_context[SharedValues::BROWSERSTACK_CUSTOM_ID] = browserstack_custom_id
end

# Validate file_path.
Expand Down Expand Up @@ -74,7 +80,8 @@ def self.details

def self.output
[
['BROWSERSTACK_APP_ID', 'App id of uploaded app.']
['BROWSERSTACK_APP_ID', 'App URL of uploaded app.'],
['BROWSERSTACK_CUSTOM_ID', 'Custom id of uploaded app. Empty string when custom_id is not set.']
]
end

Expand All @@ -100,29 +107,34 @@ def self.available_options
[
FastlaneCore::ConfigItem.new(key: :browserstack_username,
description: "BrowserStack's username",
env_name: "BROWSERSTACK_USERNAME",
optional: false,
is_string: true,
verify_block: proc do |value|
UI.user_error!("No browserstack_username given.") if value.to_s.empty?
end),
FastlaneCore::ConfigItem.new(key: :browserstack_access_key,
description: "BrowserStack's access key",
env_name: "BROWSERSTACK_ACCESS_KEY",
optional: false,
is_string: true,
verify_block: proc do |value|
UI.user_error!("No browserstack_access_key given.") if value.to_s.empty?
end),
FastlaneCore::ConfigItem.new(key: :custom_id,
description: "Custom id",
env_name: "BROWSERSTACK_CUSTOM_ID",
optional: true,
is_string: true),
FastlaneCore::ConfigItem.new(key: :file_path,
description: "Path to the app file",
env_name: "BROWSERSTACK_FILE_PATH",
optional: true,
is_string: true,
default_value: default_file_path),
FastlaneCore::ConfigItem.new(key: :ios_keychain_support,
description: "Enable/disable support for iOS keychain",
env_name: "BROWSERSTACK_IOS_KEYCHAIN_SUPPORT",
optional: true,
is_string: true)
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def self.run(params)

UI.message("Uploading app to BrowserStack AppLive...")

browserstack_app_id = Helper::BrowserstackHelper.upload_file(browserstack_username, browserstack_access_key, file_path, UPLOAD_API_ENDPOINT)
upload_response = Helper::BrowserstackHelper.upload_file(browserstack_username, browserstack_access_key, file_path, UPLOAD_API_ENDPOINT)
browserstack_app_id = upload_response["app_url"]

# Set 'BROWSERSTACK_APP_ID' environment variable, if app upload was successful.
ENV['BROWSERSTACK_LIVE_APP_ID'] = browserstack_app_id
Expand Down Expand Up @@ -58,7 +59,7 @@ def self.details

def self.output
[
['BROWSERSTACK_LIVE_APP_ID', 'App id of uploaded app.']
['BROWSERSTACK_LIVE_APP_ID', 'App URL of uploaded app.']
]
end

Expand All @@ -84,20 +85,23 @@ def self.available_options
[
FastlaneCore::ConfigItem.new(key: :browserstack_username,
description: "BrowserStack's username",
env_name: "BROWSERSTACK_USERNAME",
optional: false,
is_string: true,
verify_block: proc do |value|
UI.user_error!("No browserstack_username given.") if value.to_s.empty?
end),
FastlaneCore::ConfigItem.new(key: :browserstack_access_key,
description: "BrowserStack's access key",
env_name: "BROWSERSTACK_ACCESS_KEY",
optional: false,
is_string: true,
verify_block: proc do |value|
UI.user_error!("No browserstack_access_key given.") if value.to_s.empty?
end),
FastlaneCore::ConfigItem.new(key: :file_path,
description: "Path to the app file",
env_name: "BROWSERSTACK_FILE_PATH",
optional: true,
is_string: true,
default_value: default_file_path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def self.show_message
# +custom_id+:: Custom id for app upload.
# +file_path+:: Path to the file to be uploaded.
# +url+:: BrowserStack's app upload endpoint.
# Returns:: Hash response from BrowserStack upload API.
def self.upload_file(browserstack_username, browserstack_access_key, file_path, url, custom_id = nil, ios_keychain_support = nil)
payload = {
multipart: true,
Expand Down Expand Up @@ -47,13 +48,7 @@ def self.upload_file(browserstack_username, browserstack_access_key, file_path,
headers: headers
)

response_json = JSON.parse(response.to_s)

if !response_json["custom_id"].nil?
return response_json["custom_id"]
else
return response_json["app_url"]
end
JSON.parse(response.to_s)
rescue RestClient::ExceptionWithResponse => err
begin
error_response = JSON.parse(err.response.to_s)["error"]
Expand Down
52 changes: 45 additions & 7 deletions spec/upload_to_browserstack_app_automate_action_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
let(:custom_id) { "customId" }

before(:each) do
ENV['BROWSERSTACK_USERNAME'] = nil
ENV['BROWSERSTACK_ACCESS_KEY'] = nil
ENV['BROWSERSTACK_FILE_PATH'] = nil
ENV['BROWSERSTACK_CUSTOM_ID'] = nil
ENV['BROWSERSTACK_IOS_KEYCHAIN_SUPPORT'] = nil
ENV['BROWSERSTACK_APP_ID'] = nil
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] = nil
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = nil
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::BROWSERSTACK_APP_ID] = nil
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::BROWSERSTACK_CUSTOM_ID] = nil
end

it "raises an error if no browserstack_username is given" do
Expand Down Expand Up @@ -102,9 +110,12 @@
upload_to_browserstack_app_automate({
browserstack_username: 'browserstack_username',
browserstack_access_key: 'browserstack_access_key',
})
})
end").runner.execute(:test)
expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url")
expect(ENV['BROWSERSTACK_CUSTOM_ID']).to eq("")
expect(Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::BROWSERSTACK_APP_ID]).to eq("bs://app_url")
expect(Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::BROWSERSTACK_CUSTOM_ID]).to eq("")
end

it "should work with correct params defaulting with aab on android" do
Expand All @@ -120,7 +131,6 @@
end

it "should work with correct params and specific file_path" do
ENV['BROWSERSTACK_APP_ID'] = nil
expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json)
Fastlane::FastFile.new.parse("lane :test do
upload_to_browserstack_app_automate({
Expand All @@ -131,24 +141,27 @@
end").runner.execute(:test)

expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url")
expect(ENV['BROWSERSTACK_CUSTOM_ID']).to eq("")
expect(Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::BROWSERSTACK_CUSTOM_ID]).to eq("")
end

it "should work with custom id" do
ENV['BROWSERSTACK_APP_ID'] = nil
expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url", "custom_id" => custom_id, "shareable_id" => "username/#{custom_id}" }.to_json)
Fastlane::FastFile.new.parse("lane :test do
upload_to_browserstack_app_automate({
browserstack_username: 'username',
browserstack_access_key: 'access_key',
custom_id: '#{custom_id}',
file_path: File.join(FIXTURE_PATH, 'HelloWorld.apk')
})
file_path: File.join(FIXTURE_PATH, 'HelloWorld.apk')
})
end").runner.execute(:test)
expect(ENV['BROWSERSTACK_APP_ID']).to eq(custom_id)
expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url")
expect(ENV['BROWSERSTACK_CUSTOM_ID']).to eq(custom_id)
expect(Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::BROWSERSTACK_APP_ID]).to eq("bs://app_url")
expect(Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::BROWSERSTACK_CUSTOM_ID]).to eq(custom_id)
end

it "should work with ios keychain support" do
ENV['BROWSERSTACK_APP_ID'] = nil
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::IPA_OUTPUT_PATH] = nil
expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url", "ios_keychain_support" => true }.to_json)
fastfile = Fastlane::FastFile.new.parse("
Expand All @@ -165,6 +178,31 @@
")
fastfile.runner.execute(:test, :ios)
expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url")
expect(ENV['BROWSERSTACK_CUSTOM_ID']).to eq("")
end

it "should use env var fallback for automate params" do
ENV['BROWSERSTACK_USERNAME'] = 'username'
ENV['BROWSERSTACK_ACCESS_KEY'] = 'access_key'
ENV['BROWSERSTACK_FILE_PATH'] = File.join(FIXTURE_PATH, 'HelloWorld.apk')
ENV['BROWSERSTACK_CUSTOM_ID'] = 'env_custom_id'
ENV['BROWSERSTACK_IOS_KEYCHAIN_SUPPORT'] = 'false'

expect(RestClient::Request).to receive(:execute).with(
hash_including(
payload: hash_including(
data: '{ "custom_id": "env_custom_id" }',
ios_keychain_support: 'false'
)
)
).and_return({ "app_url" => "bs://app_url", "custom_id" => "response_custom_id" }.to_json)

Fastlane::FastFile.new.parse("lane :test do
upload_to_browserstack_app_automate({})
end").runner.execute(:test)

expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url")
expect(ENV['BROWSERSTACK_CUSTOM_ID']).to eq("response_custom_id")
end
end
end
18 changes: 17 additions & 1 deletion spec/upload_to_browserstack_app_live_action_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
describe Fastlane::Actions::UploadToBrowserstackAppLiveAction do
describe 'Upload to BrowserStack AppLive' do
before(:each) do
ENV['BROWSERSTACK_USERNAME'] = nil
ENV['BROWSERSTACK_ACCESS_KEY'] = nil
ENV['BROWSERSTACK_FILE_PATH'] = nil
ENV['BROWSERSTACK_LIVE_APP_ID'] = nil
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] = nil
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = nil
end
Expand Down Expand Up @@ -118,7 +122,6 @@
end

it "should work with correct params and specific file_path" do
ENV['BROWSERSTACK_LIVE_APP_ID'] = nil
expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json)
Fastlane::FastFile.new.parse("lane :test do
upload_to_browserstack_app_live({
Expand All @@ -129,5 +132,18 @@
end").runner.execute(:test)
expect(ENV['BROWSERSTACK_LIVE_APP_ID']).to eq("bs://app_url")
end

it "should use env var fallback for live params" do
ENV['BROWSERSTACK_USERNAME'] = 'username'
ENV['BROWSERSTACK_ACCESS_KEY'] = 'access_key'
ENV['BROWSERSTACK_FILE_PATH'] = File.join(FIXTURE_PATH, 'HelloWorld.apk')

expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json)
Fastlane::FastFile.new.parse("lane :test do
upload_to_browserstack_app_live({})
end").runner.execute(:test)

expect(ENV['BROWSERSTACK_LIVE_APP_ID']).to eq("bs://app_url")
end
end
end