├── .circleci └── config.yml ├── .github └── workflows │ └── Semgrep.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CODEOWNERS ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── fastlane-plugin-browserstack.gemspec ├── fastlane ├── Fastfile └── Pluginfile ├── lib └── fastlane │ └── plugin │ ├── browserstack.rb │ └── browserstack │ ├── actions │ ├── upload_to_browserstack_app_automate_action.rb │ └── upload_to_browserstack_app_live_action.rb │ ├── helper │ └── browserstack_helper.rb │ └── version.rb └── spec ├── fixtures ├── DummyFile1 ├── DummyFile2.txt ├── HelloWorld.aab ├── HelloWorld.apk └── HelloWorld.ipa ├── spec_helper.rb ├── upload_to_browserstack_app_automate_action_spec.rb └── upload_to_browserstack_app_live_action_spec.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Ruby CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-ruby/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/ruby:2.4.2 11 | 12 | working_directory: ~/repo 13 | 14 | steps: 15 | - checkout 16 | 17 | # Download and cache dependencies 18 | - restore_cache: 19 | keys: 20 | - v1-dependencies-{{ checksum "Gemfile" }} 21 | # fallback to using the latest cache if no exact match is found 22 | - v1-dependencies- 23 | 24 | - run: 25 | name: install dependencies 26 | command: bundle check || bundle install --jobs=4 --retry=3 --path vendor/bundle 27 | 28 | - save_cache: 29 | paths: 30 | - ./vendor 31 | key: v1-dependencies-{{ checksum "Gemfile" }} 32 | 33 | # run tests! 34 | - run: 35 | name: run tests 36 | command: bundle exec rake 37 | 38 | # collect reports 39 | - store_test_results: 40 | path: ~/repo/test-results 41 | - store_artifacts: 42 | path: ~/repo/test-results 43 | destination: test-results 44 | -------------------------------------------------------------------------------- /.github/workflows/Semgrep.yml: -------------------------------------------------------------------------------- 1 | # Name of this GitHub Actions workflow. 2 | name: Semgrep 3 | 4 | on: 5 | # Scan changed files in PRs (diff-aware scanning): 6 | # The branches below must be a subset of the branches above 7 | pull_request: 8 | branches: ["master", "main", "browserstack:master"] 9 | push: 10 | branches: ["master", "main", "browserstack:master"] 11 | schedule: 12 | - cron: '0 6 * * *' 13 | 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | semgrep: 20 | # User definable name of this GitHub Actions job. 21 | permissions: 22 | contents: read # for actions/checkout to fetch code 23 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 24 | name: semgrep/ci 25 | # If you are self-hosting, change the following `runs-on` value: 26 | runs-on: ubuntu-latest 27 | 28 | container: 29 | # A Docker image with Semgrep installed. Do not change this. 30 | image: returntocorp/semgrep 31 | 32 | # Skip any PR created by dependabot to avoid permission issues: 33 | if: (github.actor != 'dependabot[bot]') 34 | 35 | steps: 36 | # Fetch project source with GitHub Actions Checkout. 37 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 38 | # Run the "semgrep ci" command on the command line of the docker image. 39 | - run: semgrep ci --sarif --output=semgrep.sarif 40 | env: 41 | # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. 42 | SEMGREP_RULES: p/default # more at semgrep.dev/explore 43 | 44 | - name: Upload SARIF file for GitHub Advanced Security Dashboard 45 | uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 46 | with: 47 | sarif_file: semgrep.sarif 48 | if: always() 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | 4 | ## Documentation cache and generated files: 5 | /.yardoc/ 6 | /_yardoc/ 7 | /doc/ 8 | /rdoc/ 9 | fastlane/README.md 10 | fastlane/report.xml 11 | coverage 12 | test-results 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --color 3 | --format d 4 | --format RspecJunitFormatter 5 | --out test-results/rspec/rspec.xml 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Style/MultipleComparison: 3 | Enabled: false 4 | Style/PercentLiteralDelimiters: 5 | Enabled: false 6 | Style/ClassCheck: 7 | EnforcedStyle: kind_of? 8 | Style/FrozenStringLiteralComment: 9 | Enabled: false 10 | Style/SafeNavigation: 11 | Enabled: false 12 | Performance/RegexpMatch: 13 | Enabled: false 14 | Performance/StringReplacement: 15 | Enabled: false 16 | Style/NumericPredicate: 17 | Enabled: false 18 | Metrics/BlockLength: 19 | Enabled: false 20 | Metrics/ModuleLength: 21 | Enabled: false 22 | Style/VariableNumber: 23 | Enabled: false 24 | Style/MethodMissing: 25 | Enabled: false 26 | MultilineBlockChain: 27 | Enabled: false 28 | Style/NumericLiteralPrefix: 29 | Enabled: false 30 | Style/TernaryParentheses: 31 | Enabled: false 32 | Style/EmptyMethod: 33 | Enabled: false 34 | Style/BracesAroundHashParameters: 35 | Enabled: false 36 | Lint/UselessAssignment: 37 | Exclude: 38 | - "**/spec/**/*" 39 | Require/MissingRequireStatement: 40 | Exclude: 41 | - "**/spec/**/*.rb" 42 | - "**/spec_helper.rb" 43 | - spaceship/lib/spaceship/babosa_fix.rb 44 | - "**/Fastfile" 45 | - "**/*.gemspec" 46 | - rakelib/**/* 47 | - "**/*.rake" 48 | - "**/Rakefile" 49 | - fastlane/**/* 50 | - supply/**/* 51 | Layout/IndentHash: 52 | Enabled: false 53 | Layout/AlignHash: 54 | Enabled: false 55 | Layout/DotPosition: 56 | Enabled: false 57 | Style/DoubleNegation: 58 | Enabled: false 59 | Style/SymbolArray: 60 | Enabled: false 61 | Layout/IndentHeredoc: 62 | Enabled: false 63 | Style/MixinGrouping: 64 | Exclude: 65 | - "**/spec/**/*" 66 | Lint/HandleExceptions: 67 | Enabled: false 68 | Lint/UnusedBlockArgument: 69 | Enabled: false 70 | Lint/AmbiguousBlockAssociation: 71 | Enabled: false 72 | Style/GlobalVars: 73 | Enabled: false 74 | Style/ClassAndModuleChildren: 75 | Enabled: false 76 | Style/SpecialGlobalVars: 77 | Enabled: false 78 | Metrics/AbcSize: 79 | Enabled: false 80 | Metrics/MethodLength: 81 | Enabled: false 82 | Metrics/CyclomaticComplexity: 83 | Enabled: false 84 | Style/WordArray: 85 | MinSize: 19 86 | Style/SignalException: 87 | Enabled: false 88 | Style/RedundantReturn: 89 | Enabled: false 90 | Style/IfUnlessModifier: 91 | Enabled: false 92 | Style/AndOr: 93 | Enabled: true 94 | EnforcedStyle: conditionals 95 | Metrics/ClassLength: 96 | Max: 320 97 | Metrics/LineLength: 98 | Max: 370 99 | Metrics/ParameterLists: 100 | Max: 17 101 | Metrics/PerceivedComplexity: 102 | Max: 18 103 | Style/GuardClause: 104 | Enabled: false 105 | Style/StringLiterals: 106 | Enabled: false 107 | Style/ConditionalAssignment: 108 | Enabled: false 109 | Style/RedundantSelf: 110 | Enabled: false 111 | Lint/UnusedMethodArgument: 112 | Enabled: false 113 | Lint/ParenthesesAsGroupedExpression: 114 | Exclude: 115 | - "**/spec/**/*" 116 | Style/PredicateName: 117 | Enabled: false 118 | Style/PerlBackrefs: 119 | Enabled: false 120 | Layout/SpaceAroundOperators: 121 | Exclude: 122 | - "**/spec/actions_specs/xcodebuild_spec.rb" 123 | AllCops: 124 | TargetRubyVersion: 2.0 125 | Include: 126 | - "*/lib/assets/*Template" 127 | - "*/lib/assets/*TemplateAndroid" 128 | Exclude: 129 | - "**/lib/assets/custom_action_template.rb" 130 | - "./vendor/**/*" 131 | - "**/lib/assets/DefaultFastfileTemplate" 132 | Style/FileName: 133 | Exclude: 134 | - "**/Dangerfile" 135 | - "**/Brewfile" 136 | - "**/Gemfile" 137 | - "**/Podfile" 138 | - "**/Rakefile" 139 | - "**/Fastfile" 140 | - "**/Deliverfile" 141 | - "**/Snapfile" 142 | - "**/*.gemspec" 143 | Style/Documentation: 144 | Enabled: false 145 | Style/MutableConstant: 146 | Enabled: false 147 | Style/ZeroLengthPredicate: 148 | Enabled: false 149 | Style/IfInsideElse: 150 | Enabled: false 151 | Style/CollectionMethods: 152 | Enabled: false 153 | CrossPlatform/ForkUsage: 154 | Exclude: 155 | - "**/plugins/template/**/*" 156 | Style/MethodCallWithArgsParentheses: 157 | Enabled: true 158 | IgnoredMethods: 159 | - require 160 | - require_relative 161 | - gem 162 | - program 163 | - command 164 | - raise 165 | - attr_accessor 166 | - attr_reader 167 | - desc 168 | - lane 169 | - private_lane 170 | - platform 171 | - to 172 | - describe 173 | - it 174 | - be 175 | - context 176 | - before 177 | - after 178 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # os: osx # enable this if you need macOS support 2 | language: ruby 3 | rvm: 4 | - 2.2.4 5 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @browserstack/app-automate-public-repos 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source('https://rubygems.org') 2 | 3 | gemspec 4 | 5 | gem 'rest-client', '~> 2.0', '>= 2.0.2' 6 | 7 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 8 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 BrowserStack 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # browserstack-fastlane-plugin 2 | 3 | [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-browserstack) 4 | 5 | ## Getting Started 6 | 7 | This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-browserstack`, add it to your project by running: 8 | 9 | ```bash 10 | fastlane add_plugin browserstack 11 | ``` 12 | 13 | ## About 14 | 15 | Uploads IPA, APK and AAB files to BrowserStack for automation and manual testing. 16 | 17 | ## Documentation 18 | 19 | Refer [App Automate](https://www.browserstack.com/app-automate/appium/fastlane) and [App Live](https://www.browserstack.com/app-live/fastlane) for more information on using this plugin. 20 | 21 | 22 | ## Example 23 | 24 | Please refer this [sample android project](https://github.com/browserstack/browserstack-android-sample-app), which demonstrates the use of this plugin. 25 | You can easily upload your app using [BrowserStack fastlane Plugin](https://rubygems.org/gems/fastlane-plugin-browserstack) and execute your test on BrowserStack cloud. In order to configure the plugin add below action in your Fastfile. You can also check our [repository](https://github.com/browserstack/browserstack-fastlane-plugin). 26 | ``` 27 | upload_to_browserstack_app_automate( 28 | browserstack_username: ENV["BROWSERSTACK_USERNAME"], 29 | browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], 30 | file_path: "", 31 | custom_id: "" 32 | ) 33 | ``` 34 | ``` 35 | Note: custom_id is optional. You can upload multiple builds under the same custom_id. 36 | Use custom_id in 'app' capability for Appium to always pick the last uploaded build. 37 | The file_path parameter is not required if the app was built in the same lane with the help of Gradle or Gym plugin. 38 | ``` 39 | 40 | For uploading your app to AppLive use the following action in the fastfile 41 | ``` 42 | upload_to_browserstack_app_live( 43 | browserstack_username: ENV["BROWSERSTACK_USERNAME"], 44 | browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], 45 | file_path: "" 46 | ) 47 | ``` 48 | 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. 49 | 50 | 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 : 51 | ``` 52 | String app = System.getenv("BROWSERSTACK_APP_ID"); // Get app id from environment variable. 53 | capabilities.setCapability("app", app); // Add app id to driver capability. 54 | ``` 55 | 56 | 57 | ## Run tests for this plugin 58 | 59 | To run both the tests, and code style validation, run 60 | 61 | ``` 62 | rake 63 | ``` 64 | 65 | To automatically fix many of the styling issues, use 66 | ``` 67 | rubocop -a 68 | ``` 69 | 70 | ## Issues and Feedback 71 | 72 | For any other issues and feedback about this plugin, please submit it to this repository. 73 | 74 | ## Troubleshooting 75 | 76 | If you have trouble using plugins, check out the [Plugins Troubleshooting guide](https://docs.fastlane.tools/plugins/plugins-troubleshooting/). 77 | 78 | ## Using _fastlane_ Plugins 79 | 80 | For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/). 81 | 82 | ## About _fastlane_ 83 | 84 | _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). 85 | 86 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new 5 | 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new(:rubocop) 8 | 9 | task(default: [:spec, :rubocop]) 10 | -------------------------------------------------------------------------------- /fastlane-plugin-browserstack.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | lib = File.expand_path("../lib", __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'fastlane/plugin/browserstack/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'fastlane-plugin-browserstack' 9 | spec.version = Fastlane::Browserstack::VERSION 10 | spec.author = 'BrowserStack' 11 | spec.email = 'support@browserstack.com' 12 | 13 | spec.summary = 'Uploads IPA and APK files to BrowserStack for automation and manual testing.' 14 | spec.homepage = "https://github.com/browserstack/browserstack-fastlane-plugin" 15 | spec.license = "MIT" 16 | 17 | spec.files = Dir["lib/**/*"] + %w(README.md LICENSE) 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ['lib'] 20 | 21 | # Don't add a dependency to fastlane or fastlane_re 22 | # since this would cause a circular dependency 23 | 24 | # spec.add_dependency 'your-dependency', '~> 1.0.0' 25 | spec.add_runtime_dependency('rest-client', '~> 2.0', '>= 2.0.2') 26 | 27 | spec.add_development_dependency('pry') 28 | spec.add_development_dependency('bundler') 29 | spec.add_development_dependency('rspec') 30 | spec.add_development_dependency('rspec_junit_formatter') 31 | spec.add_development_dependency('rake') 32 | spec.add_development_dependency('rubocop', '0.49.1') 33 | spec.add_development_dependency('rubocop-require_tools') 34 | spec.add_development_dependency('simplecov') 35 | spec.add_development_dependency('fastlane', '>= 2.96.1') 36 | end 37 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | lane :test do 2 | upload_to_browserstack_app_automate( 3 | browserstack_username: ENV["BROWSERSTACK_USERNAME"], 4 | browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], 5 | file_path: "path_to_apk_or_ipa_file" 6 | ) 7 | upload_to_browserstack_app_live( 8 | browserstack_username: ENV["BROWSERSTACK_USERNAME"], 9 | browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], 10 | file_path: "path_to_apk_or_ipa_file" 11 | ) 12 | end 13 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/browserstack.rb: -------------------------------------------------------------------------------- 1 | require 'fastlane/plugin/browserstack/version' 2 | 3 | module Fastlane 4 | module Browserstack 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::Browserstack.all_classes.each do |current| 15 | require current 16 | end 17 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/browserstack/actions/upload_to_browserstack_app_automate_action.rb: -------------------------------------------------------------------------------- 1 | require 'fastlane/action' 2 | require_relative '../helper/browserstack_helper' 3 | require 'json' 4 | 5 | module Fastlane 6 | module Actions 7 | module SharedValues 8 | BROWSERSTACK_APP_ID ||= :BROWSERSTACK_APP_ID 9 | end 10 | class UploadToBrowserstackAppAutomateAction < Action 11 | SUPPORTED_FILE_EXTENSIONS = ["apk", "ipa", "aab"] 12 | UPLOAD_API_ENDPOINT = "https://api-cloud.browserstack.com/app-automate/upload" 13 | 14 | def self.run(params) 15 | browserstack_username = params[:browserstack_username] # Required 16 | browserstack_access_key = params[:browserstack_access_key] # Required 17 | custom_id = params[:custom_id] 18 | file_path = params[:file_path].to_s # Required 19 | ios_keychain_support = params[:ios_keychain_support] 20 | 21 | validate_file_path(file_path) 22 | validate_ios_keychain_support(ios_keychain_support) unless ios_keychain_support.nil? 23 | 24 | UI.message("Uploading app to BrowserStack AppAutomate...") 25 | 26 | browserstack_app_id = Helper::BrowserstackHelper.upload_file(browserstack_username, browserstack_access_key, file_path, UPLOAD_API_ENDPOINT, custom_id, ios_keychain_support) 27 | 28 | # Set 'BROWSERSTACK_APP_ID' environment variable, if app upload was successful. 29 | ENV['BROWSERSTACK_APP_ID'] = browserstack_app_id 30 | 31 | UI.success("Successfully uploaded app " + file_path + " to BrowserStack AppAutomate with app_url : " + browserstack_app_id) 32 | 33 | UI.success("Setting Environment variable BROWSERSTACK_APP_ID = " + browserstack_app_id) 34 | 35 | # Setting app id in SharedValues, which can be used by other fastlane actions. 36 | Actions.lane_context[SharedValues::BROWSERSTACK_APP_ID] = browserstack_app_id 37 | end 38 | 39 | # Validate file_path. 40 | def self.validate_file_path(file_path) 41 | UI.user_error!("No file found at '#{file_path}'.") unless File.exist?(file_path) 42 | 43 | # Validate file extension. 44 | file_path_parts = file_path.split(".") 45 | unless file_path_parts.length > 1 && SUPPORTED_FILE_EXTENSIONS.include?(file_path_parts.last) 46 | UI.user_error!("file_path is invalid, only files with extensions " + SUPPORTED_FILE_EXTENSIONS.to_s + " are allowed to be uploaded.") 47 | end 48 | end 49 | 50 | # Validate ios_keychain_support 51 | def self.validate_ios_keychain_support(ios_keychain_support) 52 | platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME] 53 | if !platform.nil? && platform != :ios 54 | # Check if platform is specified and not ios 55 | # If so, then raise error. 56 | UI.user_error!("ios_keychain_support param can only be used with platform ios") 57 | end 58 | unless ['true', 'false'].include?(ios_keychain_support.to_s) 59 | UI.user_error!("ios_keychain_support should be either 'true' or 'false'.") 60 | end 61 | end 62 | 63 | def self.description 64 | "Uploads IPA and APK files to BrowserStack AppAutomate for running automated tests." 65 | end 66 | 67 | def self.authors 68 | ["Hitesh Raghuvanshi"] 69 | end 70 | 71 | def self.details 72 | "Uploads IPA and APK files to BrowserStack AppAutomate for running automated tests." 73 | end 74 | 75 | def self.output 76 | [ 77 | ['BROWSERSTACK_APP_ID', 'App id of uploaded app.'] 78 | ] 79 | end 80 | 81 | def self.default_file_path 82 | platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME] 83 | if platform == :ios 84 | # Shared value for ipa path if it was generated by gym https://docs.fastlane.tools/actions/gym/. 85 | return Actions.lane_context[Actions::SharedValues::IPA_OUTPUT_PATH] 86 | else 87 | # Shared value for apk/aab if it was generated by gradle. 88 | apk_path = Actions.lane_context[Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] 89 | aab_path = Actions.lane_context[Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] 90 | 91 | if !apk_path.nil? 92 | return apk_path 93 | else 94 | return aab_path 95 | end 96 | end 97 | end 98 | 99 | def self.available_options 100 | [ 101 | FastlaneCore::ConfigItem.new(key: :browserstack_username, 102 | description: "BrowserStack's username", 103 | optional: false, 104 | is_string: true, 105 | verify_block: proc do |value| 106 | UI.user_error!("No browserstack_username given.") if value.to_s.empty? 107 | end), 108 | FastlaneCore::ConfigItem.new(key: :browserstack_access_key, 109 | description: "BrowserStack's access key", 110 | optional: false, 111 | is_string: true, 112 | verify_block: proc do |value| 113 | UI.user_error!("No browserstack_access_key given.") if value.to_s.empty? 114 | end), 115 | FastlaneCore::ConfigItem.new(key: :custom_id, 116 | description: "Custom id", 117 | optional: true, 118 | is_string: true), 119 | FastlaneCore::ConfigItem.new(key: :file_path, 120 | description: "Path to the app file", 121 | optional: true, 122 | is_string: true, 123 | default_value: default_file_path), 124 | FastlaneCore::ConfigItem.new(key: :ios_keychain_support, 125 | description: "Enable/disable support for iOS keychain", 126 | optional: true, 127 | is_string: true) 128 | ] 129 | end 130 | 131 | def self.is_supported?(platform) 132 | [:ios, :android].include?(platform) 133 | end 134 | 135 | def self.example_code 136 | [ 137 | 'upload_to_browserstack_app_automate', 138 | 'upload_to_browserstack_app_automate( 139 | browserstack_username: ENV["BROWSERSTACK_USERNAME"], 140 | browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], 141 | file_path: "path_to_apk_or_ipa_file" 142 | )' 143 | ] 144 | end 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/browserstack/actions/upload_to_browserstack_app_live_action.rb: -------------------------------------------------------------------------------- 1 | require 'fastlane/action' 2 | require_relative '../helper/browserstack_helper' 3 | require 'json' 4 | 5 | module Fastlane 6 | module Actions 7 | module SharedValues 8 | BROWSERSTACK_LIVE_APP_ID ||= :BROWSERSTACK_LIVE_APP_ID 9 | end 10 | class UploadToBrowserstackAppLiveAction < Action 11 | SUPPORTED_FILE_EXTENSIONS = ["apk", "ipa", "aab"] 12 | UPLOAD_API_ENDPOINT = "https://api-cloud.browserstack.com/app-live/upload" 13 | 14 | def self.run(params) 15 | browserstack_username = params[:browserstack_username] # Required 16 | browserstack_access_key = params[:browserstack_access_key] # Required 17 | file_path = params[:file_path].to_s # Required 18 | 19 | validate_file_path(file_path) 20 | 21 | UI.message("Uploading app to BrowserStack AppLive...") 22 | 23 | browserstack_app_id = Helper::BrowserstackHelper.upload_file(browserstack_username, browserstack_access_key, file_path, UPLOAD_API_ENDPOINT) 24 | 25 | # Set 'BROWSERSTACK_APP_ID' environment variable, if app upload was successful. 26 | ENV['BROWSERSTACK_LIVE_APP_ID'] = browserstack_app_id 27 | 28 | UI.success("Successfully uploaded app " + file_path + " to BrowserStack AppLive with app_url : " + browserstack_app_id) 29 | 30 | UI.success("Setting Environment variable BROWSERSTACK_LIVE_APP_ID = " + browserstack_app_id) 31 | 32 | # Setting app id in SharedValues, which can be used by other fastlane actions. 33 | Actions.lane_context[SharedValues::BROWSERSTACK_LIVE_APP_ID] = browserstack_app_id 34 | end 35 | 36 | # Validate file_path. 37 | def self.validate_file_path(file_path) 38 | UI.user_error!("No file found at '#{file_path}'.") unless File.exist?(file_path) 39 | 40 | # Validate file extension. 41 | file_path_parts = file_path.split(".") 42 | unless file_path_parts.length > 1 && SUPPORTED_FILE_EXTENSIONS.include?(file_path_parts.last) 43 | UI.user_error!("file_path is invalid, only files with extensions " + SUPPORTED_FILE_EXTENSIONS.to_s + " are allowed to be uploaded.") 44 | end 45 | end 46 | 47 | def self.description 48 | "Uploads IPA and APK files to BrowserStack AppLive for running manual tests." 49 | end 50 | 51 | def self.authors 52 | ["Hitesh Raghuvanshi"] 53 | end 54 | 55 | def self.details 56 | "Uploads IPA and APK files to BrowserStack AppLive for running manual tests." 57 | end 58 | 59 | def self.output 60 | [ 61 | ['BROWSERSTACK_LIVE_APP_ID', 'App id of uploaded app.'] 62 | ] 63 | end 64 | 65 | def self.default_file_path 66 | platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME] 67 | if platform == :ios 68 | # Shared value for ipa path if it was generated by gym https://docs.fastlane.tools/actions/gym/. 69 | return Actions.lane_context[Actions::SharedValues::IPA_OUTPUT_PATH] 70 | else 71 | # Shared value for apk/aab if it was generated by gradle. 72 | apk_path = Actions.lane_context[Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] 73 | aab_path = Actions.lane_context[Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] 74 | 75 | if !apk_path.nil? 76 | return apk_path 77 | else 78 | return aab_path 79 | end 80 | end 81 | end 82 | 83 | def self.available_options 84 | [ 85 | FastlaneCore::ConfigItem.new(key: :browserstack_username, 86 | description: "BrowserStack's username", 87 | optional: false, 88 | is_string: true, 89 | verify_block: proc do |value| 90 | UI.user_error!("No browserstack_username given.") if value.to_s.empty? 91 | end), 92 | FastlaneCore::ConfigItem.new(key: :browserstack_access_key, 93 | description: "BrowserStack's access key", 94 | optional: false, 95 | is_string: true, 96 | verify_block: proc do |value| 97 | UI.user_error!("No browserstack_access_key given.") if value.to_s.empty? 98 | end), 99 | FastlaneCore::ConfigItem.new(key: :file_path, 100 | description: "Path to the app file", 101 | optional: true, 102 | is_string: true, 103 | default_value: default_file_path) 104 | ] 105 | end 106 | 107 | def self.is_supported?(platform) 108 | [:ios, :android].include?(platform) 109 | end 110 | 111 | def self.example_code 112 | [ 113 | 'upload_to_browserstack_app_live', 114 | 'upload_to_browserstack_app_live( 115 | browserstack_username: ENV["BROWSERSTACK_USERNAME"], 116 | browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], 117 | file_path: "path_to_apk_or_ipa_file" 118 | )' 119 | ] 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/browserstack/helper/browserstack_helper.rb: -------------------------------------------------------------------------------- 1 | require 'fastlane_core/ui/ui' 2 | require 'rest-client' 3 | 4 | module Fastlane 5 | UI = FastlaneCore::UI unless Fastlane.const_defined?("UI") 6 | 7 | module Helper 8 | class BrowserstackHelper 9 | # class methods that you define here become available in your action 10 | # as `Helper::BrowserstackHelper.your_method` 11 | # 12 | def self.show_message 13 | UI.message("Hello from the browserstack plugin helper!") 14 | end 15 | 16 | # Uploads file to BrowserStack 17 | # Params : 18 | # +browserstack_username+:: BrowserStack's username. 19 | # +browserstack_access_key+:: BrowserStack's access key. 20 | # +custom_id+:: Custom id for app upload. 21 | # +file_path+:: Path to the file to be uploaded. 22 | # +url+:: BrowserStack's app upload endpoint. 23 | def self.upload_file(browserstack_username, browserstack_access_key, file_path, url, custom_id = nil, ios_keychain_support = nil) 24 | payload = { 25 | multipart: true, 26 | file: File.new(file_path, 'rb') 27 | } 28 | 29 | unless custom_id.nil? 30 | payload[:data] = '{ "custom_id": "' + custom_id + '" }' 31 | end 32 | 33 | unless ios_keychain_support.nil? 34 | payload[:ios_keychain_support] = ios_keychain_support 35 | end 36 | 37 | headers = { 38 | "User-Agent" => "browserstack_fastlane_plugin" 39 | } 40 | begin 41 | response = RestClient::Request.execute( 42 | method: :post, 43 | url: url, 44 | user: browserstack_username, 45 | password: browserstack_access_key, 46 | payload: payload, 47 | headers: headers 48 | ) 49 | 50 | response_json = JSON.parse(response.to_s) 51 | 52 | if !response_json["custom_id"].nil? 53 | return response_json["custom_id"] 54 | else 55 | return response_json["app_url"] 56 | end 57 | rescue RestClient::ExceptionWithResponse => err 58 | begin 59 | error_response = JSON.parse(err.response.to_s)["error"] 60 | rescue 61 | error_response = "Internal server error" 62 | end 63 | # Give error if upload failed. 64 | UI.user_error!("App upload failed!!! Reason : #{error_response}") 65 | rescue StandardError => error 66 | UI.user_error!("App upload failed!!! Reason : #{error.message}") 67 | end 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/browserstack/version.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Browserstack 3 | VERSION = "0.3.4" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/DummyFile1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserstack/browserstack-fastlane-plugin/8b592dd95d3d17e66b8d3aa4fa01bbefe14ec756/spec/fixtures/DummyFile1 -------------------------------------------------------------------------------- /spec/fixtures/DummyFile2.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserstack/browserstack-fastlane-plugin/8b592dd95d3d17e66b8d3aa4fa01bbefe14ec756/spec/fixtures/DummyFile2.txt -------------------------------------------------------------------------------- /spec/fixtures/HelloWorld.aab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserstack/browserstack-fastlane-plugin/8b592dd95d3d17e66b8d3aa4fa01bbefe14ec756/spec/fixtures/HelloWorld.aab -------------------------------------------------------------------------------- /spec/fixtures/HelloWorld.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserstack/browserstack-fastlane-plugin/8b592dd95d3d17e66b8d3aa4fa01bbefe14ec756/spec/fixtures/HelloWorld.apk -------------------------------------------------------------------------------- /spec/fixtures/HelloWorld.ipa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserstack/browserstack-fastlane-plugin/8b592dd95d3d17e66b8d3aa4fa01bbefe14ec756/spec/fixtures/HelloWorld.ipa -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) 2 | 3 | require 'simplecov' 4 | 5 | # SimpleCov.minimum_coverage 95 6 | SimpleCov.start 7 | 8 | # This module is only used to check the environment is currently a testing env 9 | module SpecHelper 10 | end 11 | 12 | require 'fastlane' # to import the Action super class 13 | require 'fastlane/plugin/browserstack' # import the actual plugin 14 | 15 | Fastlane.load_actions # load other actions (in case your plugin calls other actions or shared values) 16 | 17 | FIXTURE_PATH = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures')) 18 | -------------------------------------------------------------------------------- /spec/upload_to_browserstack_app_automate_action_spec.rb: -------------------------------------------------------------------------------- 1 | describe Fastlane::Actions::UploadToBrowserstackAppAutomateAction do 2 | describe 'Upload to BrowserStack AppAutomate' do 3 | let(:custom_id) { "customId" } 4 | 5 | before(:each) do 6 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] = nil 7 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = nil 8 | end 9 | 10 | it "raises an error if no browserstack_username is given" do 11 | expect do 12 | Fastlane::FastFile.new.parse("lane :test do 13 | upload_to_browserstack_app_automate({}) 14 | end").runner.execute(:test) 15 | end.to raise_error("No browserstack_username given.") 16 | end 17 | 18 | it "raises an error if no browserstack_access_key is given" do 19 | expect do 20 | Fastlane::FastFile.new.parse("lane :test do 21 | upload_to_browserstack_app_automate({ 22 | browserstack_username: 'browserstack_username' 23 | }) 24 | end").runner.execute(:test) 25 | end.to raise_error("No browserstack_access_key given.") 26 | end 27 | 28 | it "raises an error if no file_path is given" do 29 | expect do 30 | Fastlane::FastFile.new.parse("lane :test do 31 | upload_to_browserstack_app_automate({ 32 | browserstack_username: 'browserstack_username', 33 | browserstack_access_key: 'browserstack_access_key' 34 | }) 35 | end").runner.execute(:test) 36 | end.to raise_error("No file found at ''.") 37 | end 38 | 39 | it "raises an error if no file present at given file_path" do 40 | expect do 41 | Fastlane::FastFile.new.parse("lane :test do 42 | upload_to_browserstack_app_automate({ 43 | browserstack_username: 'browserstack_username', 44 | browserstack_access_key: 'browserstack_access_key', 45 | file_path: 'some/random/non/existing/file/path' 46 | }) 47 | end").runner.execute(:test) 48 | end.to raise_error("No file found at 'some/random/non/existing/file/path'.") 49 | end 50 | 51 | it "raises an error if file_path do not have any file extension" do 52 | expect do 53 | Fastlane::FastFile.new.parse("lane :test do 54 | upload_to_browserstack_app_automate({ 55 | browserstack_username: 'browserstack_username', 56 | browserstack_access_key: 'browserstack_access_key', 57 | file_path: File.join(FIXTURE_PATH, 'DummyFile1') 58 | }) 59 | end").runner.execute(:test) 60 | end.to raise_error('file_path is invalid, only files with extensions ["apk", "ipa", "aab"] are allowed to be uploaded.') 61 | end 62 | 63 | it "raises an error if file_path have invalid file extension" do 64 | expect do 65 | Fastlane::FastFile.new.parse("lane :test do 66 | upload_to_browserstack_app_automate({ 67 | browserstack_username: 'browserstack_username', 68 | browserstack_access_key: 'browserstack_access_key', 69 | file_path: File.join(FIXTURE_PATH, 'DummyFile2.txt') 70 | }) 71 | end").runner.execute(:test) 72 | end.to raise_error('file_path is invalid, only files with extensions ["apk", "ipa", "aab"] are allowed to be uploaded.') 73 | end 74 | 75 | it "raises an error if browserstack credentials are wrong" do 76 | expect do 77 | Fastlane::FastFile.new.parse("lane :test do 78 | upload_to_browserstack_app_automate({ 79 | browserstack_username: 'browserstack_username', 80 | browserstack_access_key: 'browserstack_access_key', 81 | file_path: File.join(FIXTURE_PATH, 'HelloWorld.apk') 82 | }) 83 | end").runner.execute(:test) 84 | end.to raise_error(/App upload failed!!! Reason :/) 85 | end 86 | 87 | it "raises an error if neither GRADLE_APK_OUTPUT_PATH or GRADLE_AAB_OUTPUT_PATH available on android" do 88 | expect do 89 | Fastlane::FastFile.new.parse("lane :test do 90 | upload_to_browserstack_app_automate({ 91 | browserstack_username: 'browserstack_username', 92 | browserstack_access_key: 'browserstack_access_key', 93 | }) 94 | end").runner.execute(:test) 95 | end.to raise_error('No file found at \'\'.') 96 | end 97 | 98 | it "should work with correct params defaulting with apk on android" do 99 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] = File.join(FIXTURE_PATH, 'HelloWorld.apk') 100 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json) 101 | Fastlane::FastFile.new.parse("lane :test do 102 | upload_to_browserstack_app_automate({ 103 | browserstack_username: 'browserstack_username', 104 | browserstack_access_key: 'browserstack_access_key', 105 | }) 106 | end").runner.execute(:test) 107 | expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url") 108 | end 109 | 110 | it "should work with correct params defaulting with aab on android" do 111 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = File.join(FIXTURE_PATH, 'HelloWorld.aab') 112 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json) 113 | Fastlane::FastFile.new.parse("lane :test do 114 | upload_to_browserstack_app_automate({ 115 | browserstack_username: 'browserstack_username', 116 | browserstack_access_key: 'browserstack_access_key', 117 | }) 118 | end").runner.execute(:test) 119 | expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url") 120 | end 121 | 122 | it "should work with correct params and specific file_path" do 123 | ENV['BROWSERSTACK_APP_ID'] = nil 124 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json) 125 | Fastlane::FastFile.new.parse("lane :test do 126 | upload_to_browserstack_app_automate({ 127 | browserstack_username: 'browserstack_username', 128 | browserstack_access_key: 'browserstack_access_key', 129 | file_path: File.join(FIXTURE_PATH, 'HelloWorld.apk') 130 | }) 131 | end").runner.execute(:test) 132 | 133 | expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url") 134 | end 135 | 136 | it "should work with custom id" do 137 | ENV['BROWSERSTACK_APP_ID'] = nil 138 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url", "custom_id" => custom_id, "shareable_id" => "username/#{custom_id}" }.to_json) 139 | Fastlane::FastFile.new.parse("lane :test do 140 | upload_to_browserstack_app_automate({ 141 | browserstack_username: 'username', 142 | browserstack_access_key: 'access_key', 143 | custom_id: '#{custom_id}', 144 | file_path: File.join(FIXTURE_PATH, 'HelloWorld.apk') 145 | }) 146 | end").runner.execute(:test) 147 | expect(ENV['BROWSERSTACK_APP_ID']).to eq(custom_id) 148 | end 149 | 150 | it "should work with ios keychain support" do 151 | ENV['BROWSERSTACK_APP_ID'] = nil 152 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::IPA_OUTPUT_PATH] = nil 153 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url", "ios_keychain_support" => true }.to_json) 154 | fastfile = Fastlane::FastFile.new.parse(" 155 | platform :ios do 156 | lane :test do 157 | upload_to_browserstack_app_automate({ 158 | browserstack_username: 'username', 159 | browserstack_access_key: 'access_key', 160 | ios_keychain_support: 'true', 161 | file_path: File.join(FIXTURE_PATH, 'HelloWorld.ipa') 162 | }) 163 | end 164 | end 165 | ") 166 | fastfile.runner.execute(:test, :ios) 167 | expect(ENV['BROWSERSTACK_APP_ID']).to eq("bs://app_url") 168 | end 169 | end 170 | end 171 | -------------------------------------------------------------------------------- /spec/upload_to_browserstack_app_live_action_spec.rb: -------------------------------------------------------------------------------- 1 | describe Fastlane::Actions::UploadToBrowserstackAppLiveAction do 2 | describe 'Upload to BrowserStack AppLive' do 3 | before(:each) do 4 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] = nil 5 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = nil 6 | end 7 | 8 | it "raises an error if no browserstack_username is given" do 9 | expect do 10 | Fastlane::FastFile.new.parse("lane :test do 11 | upload_to_browserstack_app_live({}) 12 | end").runner.execute(:test) 13 | end.to raise_error("No browserstack_username given.") 14 | end 15 | 16 | it "raises an error if no browserstack_access_key is given" do 17 | expect do 18 | Fastlane::FastFile.new.parse("lane :test do 19 | upload_to_browserstack_app_live({ 20 | browserstack_username: 'browserstack_username' 21 | }) 22 | end").runner.execute(:test) 23 | end.to raise_error("No browserstack_access_key given.") 24 | end 25 | 26 | it "raises an error if no file_path is given" do 27 | expect do 28 | Fastlane::FastFile.new.parse("lane :test do 29 | upload_to_browserstack_app_live({ 30 | browserstack_username: 'browserstack_username', 31 | browserstack_access_key: 'browserstack_access_key' 32 | }) 33 | end").runner.execute(:test) 34 | end.to raise_error("No file found at ''.") 35 | end 36 | 37 | it "raises an error if no file present at given file_path" do 38 | expect do 39 | Fastlane::FastFile.new.parse("lane :test do 40 | upload_to_browserstack_app_live({ 41 | browserstack_username: 'browserstack_username', 42 | browserstack_access_key: 'browserstack_access_key', 43 | file_path: 'some/random/non/existing/file/path' 44 | }) 45 | end").runner.execute(:test) 46 | end.to raise_error("No file found at 'some/random/non/existing/file/path'.") 47 | end 48 | 49 | it "raises an error if file_path do not have any file extension" do 50 | expect do 51 | Fastlane::FastFile.new.parse("lane :test do 52 | upload_to_browserstack_app_live({ 53 | browserstack_username: 'browserstack_username', 54 | browserstack_access_key: 'browserstack_access_key', 55 | file_path: File.join(FIXTURE_PATH, 'DummyFile1') 56 | }) 57 | end").runner.execute(:test) 58 | end.to raise_error('file_path is invalid, only files with extensions ["apk", "ipa", "aab"] are allowed to be uploaded.') 59 | end 60 | 61 | it "raises an error if file_path have invalid file extension" do 62 | expect do 63 | Fastlane::FastFile.new.parse("lane :test do 64 | upload_to_browserstack_app_live({ 65 | browserstack_username: 'browserstack_username', 66 | browserstack_access_key: 'browserstack_access_key', 67 | file_path: File.join(FIXTURE_PATH, 'DummyFile2.txt') 68 | }) 69 | end").runner.execute(:test) 70 | end.to raise_error('file_path is invalid, only files with extensions ["apk", "ipa", "aab"] are allowed to be uploaded.') 71 | end 72 | 73 | it "raises an error if browserstack credentials are wrong" do 74 | expect do 75 | Fastlane::FastFile.new.parse("lane :test do 76 | upload_to_browserstack_app_live({ 77 | browserstack_username: 'browserstack_username', 78 | browserstack_access_key: 'browserstack_access_key', 79 | file_path: File.join(FIXTURE_PATH, 'HelloWorld.apk') 80 | }) 81 | end").runner.execute(:test) 82 | end.to raise_error(/App upload failed!!! Reason :/) 83 | end 84 | 85 | it "raises an error if neither GRADLE_APK_OUTPUT_PATH or GRADLE_AAB_OUTPUT_PATH available on android" do 86 | expect do 87 | Fastlane::FastFile.new.parse("lane :test do 88 | upload_to_browserstack_app_live({ 89 | browserstack_username: 'browserstack_username', 90 | browserstack_access_key: 'browserstack_access_key', 91 | }) 92 | end").runner.execute(:test) 93 | end.to raise_error('No file found at \'\'.') 94 | end 95 | 96 | it "should work with correct params defaulting with apk on android" do 97 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] = File.join(FIXTURE_PATH, 'HelloWorld.apk') 98 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json) 99 | Fastlane::FastFile.new.parse("lane :test do 100 | upload_to_browserstack_app_live({ 101 | browserstack_username: 'username', 102 | browserstack_access_key: 'access_key', 103 | }) 104 | end").runner.execute(:test) 105 | expect(ENV['BROWSERSTACK_LIVE_APP_ID']).to eq("bs://app_url") 106 | end 107 | 108 | it "should work with correct params defaulting with aab on android" do 109 | Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = File.join(FIXTURE_PATH, 'HelloWorld.aab') 110 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json) 111 | Fastlane::FastFile.new.parse("lane :test do 112 | upload_to_browserstack_app_live({ 113 | browserstack_username: 'username', 114 | browserstack_access_key: 'access_key', 115 | }) 116 | end").runner.execute(:test) 117 | expect(ENV['BROWSERSTACK_LIVE_APP_ID']).to eq("bs://app_url") 118 | end 119 | 120 | it "should work with correct params and specific file_path" do 121 | ENV['BROWSERSTACK_LIVE_APP_ID'] = nil 122 | expect(RestClient::Request).to receive(:execute).and_return({ "app_url" => "bs://app_url" }.to_json) 123 | Fastlane::FastFile.new.parse("lane :test do 124 | upload_to_browserstack_app_live({ 125 | browserstack_username: 'username', 126 | browserstack_access_key: 'access_key', 127 | file_path: File.join(FIXTURE_PATH, 'HelloWorld.apk') 128 | }) 129 | end").runner.execute(:test) 130 | expect(ENV['BROWSERSTACK_LIVE_APP_ID']).to eq("bs://app_url") 131 | end 132 | end 133 | end 134 | --------------------------------------------------------------------------------