kraken-mobile 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +8 -0
  3. data/README.md +169 -0
  4. data/bin/kraken-mobile +91 -0
  5. data/bin/kraken-mobile-calabash-android.rb +85 -0
  6. data/bin/kraken-mobile-generate.rb +19 -0
  7. data/bin/kraken-mobile-helpers.rb +48 -0
  8. data/bin/kraken-mobile-setup.rb +50 -0
  9. data/calabash-android-features-skeleton/my_first.feature +11 -0
  10. data/calabash-android-features-skeleton/step_definitions/kraken_steps.rb +1 -0
  11. data/calabash-android-features-skeleton/support/app_installation_hooks.rb +25 -0
  12. data/calabash-android-features-skeleton/support/app_life_cycle_hooks.rb +10 -0
  13. data/calabash-android-features-skeleton/support/env.rb +1 -0
  14. data/lib/kraken-mobile.rb +29 -0
  15. data/lib/kraken-mobile/constants.rb +25 -0
  16. data/lib/kraken-mobile/helpers/command_helper.rb +38 -0
  17. data/lib/kraken-mobile/helpers/devices_helper/adb_helper.rb +157 -0
  18. data/lib/kraken-mobile/helpers/devices_helper/manager.rb +43 -0
  19. data/lib/kraken-mobile/helpers/feature_analyzer.rb +28 -0
  20. data/lib/kraken-mobile/helpers/feature_grouper.rb +48 -0
  21. data/lib/kraken-mobile/helpers/reporter.rb +381 -0
  22. data/lib/kraken-mobile/models/device.rb +32 -0
  23. data/lib/kraken-mobile/protocols/file_protocol.rb +126 -0
  24. data/lib/kraken-mobile/runners/calabash/android/android_runner.rb +130 -0
  25. data/lib/kraken-mobile/runners/calabash/android/apk_signer.rb +14 -0
  26. data/lib/kraken-mobile/runners/calabash/android/cucumber.rb +27 -0
  27. data/lib/kraken-mobile/runners/calabash/android/kraken_hooks.rb +15 -0
  28. data/lib/kraken-mobile/runners/calabash/android/kraken_steps.rb +3 -0
  29. data/lib/kraken-mobile/runners/calabash/android/monkey_helper.rb +139 -0
  30. data/lib/kraken-mobile/runners/calabash/android/operations.rb +44 -0
  31. data/lib/kraken-mobile/runners/calabash/android/steps/communication_steps.rb +57 -0
  32. data/lib/kraken-mobile/runners/calabash/monkey/monkey_runner.rb +113 -0
  33. data/lib/kraken-mobile/runners/runner.rb +18 -0
  34. data/lib/kraken-mobile/version.rb +5 -0
  35. data/reporter/assets/css/bootstrap.min.css +6 -0
  36. data/reporter/assets/css/dataTables.bootstrap.min.css +1 -0
  37. data/reporter/assets/css/feature_index.css +449 -0
  38. data/reporter/assets/css/font-awesome.min.css +4 -0
  39. data/reporter/assets/css/responsive.dataTables.min.css +1 -0
  40. data/reporter/assets/css/scenario_index.css +461 -0
  41. data/reporter/assets/fonts/FontAwesome.otf +0 -0
  42. data/reporter/assets/fonts/fontawesome-webfont.eot +0 -0
  43. data/reporter/assets/fonts/fontawesome-webfont.svg +2671 -0
  44. data/reporter/assets/fonts/fontawesome-webfont.ttf +0 -0
  45. data/reporter/assets/fonts/fontawesome-webfont.woff +0 -0
  46. data/reporter/assets/fonts/fontawesome-webfont.woff2 +0 -0
  47. data/reporter/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  48. data/reporter/assets/fonts/glyphicons-halflings-regular.svg +288 -0
  49. data/reporter/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  50. data/reporter/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  51. data/reporter/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
  52. data/reporter/assets/images/kraken.png +0 -0
  53. data/reporter/assets/js/Chart.min.js +14 -0
  54. data/reporter/assets/js/bootstrap.min.js +7 -0
  55. data/reporter/assets/js/d3.chart.min.js +7 -0
  56. data/reporter/assets/js/d3.v3.min.js +5 -0
  57. data/reporter/assets/js/dataTables.bootstrap.min.js +8 -0
  58. data/reporter/assets/js/dataTables.responsive.min.js +26 -0
  59. data/reporter/assets/js/first-sankey.js +292 -0
  60. data/reporter/assets/js/gitgraph.min.js +10 -0
  61. data/reporter/assets/js/html5shiv.min.js +4 -0
  62. data/reporter/assets/js/jquery-3.2.1.min.js +4 -0
  63. data/reporter/assets/js/jquery.dataTables.min.js +167 -0
  64. data/reporter/assets/js/respond.min.js +5 -0
  65. data/reporter/assets/js/sankey.js +512 -0
  66. data/reporter/feature_report.html.erb +274 -0
  67. data/reporter/index.html.erb +711 -0
  68. data/reporter/scenario_report.html.erb +174 -0
  69. metadata +169 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4bfa46c21f03853b64f32b256ba62bf2fc4083da
4
+ data.tar.gz: 85837852d965698ffa5d9b1740dbc02f893265bc
5
+ SHA512:
6
+ metadata.gz: 8a46f5c652991f58a69bf6a4df6a87f76078e90055077acf16cdb1d00916629c9664dbacfe14a361dffd21b713fca01349809dfe27a9022c155e06865027a827
7
+ data.tar.gz: e1c1b7ae29b49f6094c1b73598235ff2743523fff4f0c73b0eea3fe6e4816d5e1f63e8640a174c75722194598fc4a5c0e6e5bf57cf9258779dc8793e4bc56f13
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Kraken-Mobile Copyright (2019) Ravelinx22. All rights reserved.
2
+ The use and distribution terms for this software are covered by the
3
+ Eclipse Public License 1.0
4
+ (http://opensource.org/licenses/eclipse-1.0.php) which can be found in
5
+ the file epl-v10.html at the root of this distribution. By using this
6
+ software in any fashion, you are agreeing to be bound by the terms of
7
+ this license. You must not remove this notice, or any other, from
8
+ this software.
data/README.md ADDED
@@ -0,0 +1,169 @@
1
+ <p align="center">
2
+ <a href="https://getbootstrap.com/">
3
+ <img src="https://raw.githubusercontent.com/ravelinx22/KrakenMobile/master/reporter/assets/images/kraken.png" alt="Bootstrap logo" width="140" height="193">
4
+ </a>
5
+ </p>
6
+
7
+ <h3 align="center">Kraken Mobile</h3>
8
+
9
+ <p align="center">Kraken is an open source automated android E2E testing tool that supports and validates scenarios that involve the inter-communication between two or more users. It works in a Black Box manner meaning that it’s not required to have access to the source code of the application but instead it can be run with the APK (Android package file format). Kraken uses signaling for coordinating the communication between the devices using a file based protocol.</p>
10
+
11
+ ## Table of contents
12
+
13
+ - [Technologies](#technologies)
14
+ - [Installation](#installation)
15
+ - [Signaling](#signaling)
16
+ - [Writing your first test](#writing-your-first-test)
17
+ - [Kraken steps](#kraken-steps)
18
+ - [Running your tests](#running-your-tests)
19
+ - [Kraken Settings](#kraken-settings)
20
+ - [Properties file](#properties-file)
21
+
22
+ ## Technologies
23
+
24
+ Kraken uses [calabash-android](https://github.com/calabash/calabash-android) for running automated E2E tests in each device or emulator and [cucumber](https://github.com/cucumber/cucumber-ruby) for running your feature files written with Gherkin sintax.
25
+
26
+ ## Installation
27
+
28
+ ### Prerequisites
29
+
30
+ Kraken requires Ruby 2.20 or higher but we recommend using ~2.3 version. We use calabash-android as runner, you can check their prerequisites at this [link](https://github.com/calabash/calabash-android/blob/master/documentation/installation.md)
31
+
32
+
33
+ Installing and managing a Gem is done through the gem command. To install Kraken's gem run the following command
34
+
35
+ $ gem install kraken-mobile
36
+
37
+
38
+ ## Signaling
39
+
40
+ Signaling is a protocol used for the communication of two or more devices running in parallel. It’s based in the idea that each emulator or real device has a communication channel where he can receive signals sent from other devices which contain information or actions that are supposed to be executed. This type of protocol is commonly used in automated mobile E2E testing tools that validate scenarios involving the inter-communication and collaboration of two or more applications.
41
+
42
+ ## Writing your first test
43
+
44
+ ### Generate cucumber feature skeleton
45
+
46
+ First you need to generate the cucumber feature skeleton where your tests are going to be saved. To achieve this you should run `kraken-mobile gen`. It will create the skeleton in your current folder like this:
47
+
48
+ features
49
+ |_support
50
+ | |_app_installation_hooks.rb
51
+ | |_app_life_cycle_hooks.rb
52
+ | |_env.rb
53
+ |_step_definitions
54
+ | |_kraken_steps.rb
55
+ |_my_first.feature
56
+
57
+ ### Write a test
58
+
59
+ The features goes in the features foler and should have the ".feature" extension.
60
+
61
+ You can start out by looking at features/my_first.feature. You should also check calabash [predefined steps](https://github.com/calabash/calabash-android/blob/master/ruby-gem/lib/calabash-android/canned_steps.md).
62
+
63
+ ### Syntax
64
+
65
+ In Kraken each feature is a test and each scenario within a feature is a test case that is run in a device. Each device is identified as an user and numbered from 1 to N. Ex: @user1, @user2, @user3. To check what is the number of a given device you should run `kraken-mobile devices`
66
+
67
+ List of devices attached
68
+ user1 - emulator-5554 - Android_SDK_built_for_x86
69
+ user2 - emulator-5556 - Android_SDK_built_for_x86
70
+
71
+ After identifying what number each device has, you can write your test case giving each scenario the tag of a given device like so:
72
+
73
+ Feature: Example feature
74
+
75
+ @user1
76
+ Scenario: As a first user I say hi to a second user
77
+ Given I wait
78
+ Then I send a signal to user 2 containing "hi"
79
+
80
+ @user2
81
+ Scenario: As a second user I wait for user 1 to say hi
82
+ Given I wait for a signal containing "hi"
83
+ Then I wait
84
+
85
+ ## Kraken steps
86
+
87
+ Kraken offers two main steps to help synchronizing your devices.
88
+
89
+ ### Signaling steps
90
+
91
+ To wait for a signal coming from another device for 10 seconds that is Kraken default timeout use the following step.
92
+
93
+ Then /^I wait for a signal containing "([^\"]*)"$/
94
+
95
+ To wait for a signal coming from another device for an specified number of seconds use the following step
96
+
97
+ Then /^I wait for a signal containing "([^\"]*)" for (\d+) seconds$/
98
+
99
+ To send a signal to another specified device use the following step
100
+
101
+ Then /^I send a signal to user (\d+) containing "([^\"]*)"$/
102
+
103
+ ### Signaling functions
104
+
105
+ Kraken internal implementation of the signaling steps use the following functions.
106
+
107
+ #### readSignal(channel, content, timeout)
108
+
109
+ Waits for a signal with the specified content in the channel passed by parameter. This functions waits for the specified number of seconds in the timeout parameter before throwing an exception.
110
+
111
+ **Note: The channel parameter has to be the number of a device such as @user1, @user2, @userN**
112
+
113
+ #### writeSignal(channel, content)
114
+
115
+ Writes content to a channel passed by parameter.
116
+
117
+ **Note: The channel parameter has to be the number of a device such as @user1, @user2, @userN**
118
+
119
+ ## Running your tests
120
+
121
+ To run your test:
122
+
123
+ kraken-mobile run <apk>
124
+
125
+ Kraken with the help of Calabash-Android will install an instrumentation along with your app and will start your tests in all devices connected (Check Kraken Settings section in order to learn how to specify in what devices your tests should be run).
126
+
127
+ ## Kraken Settings
128
+
129
+ Kraken uses kraken_mobile_settings.json to specify in what devices the tests should be run.
130
+
131
+ ### Generate settings file
132
+
133
+ The following command will show you the available connected devices or emulators and let you choose which ones you want to use.
134
+
135
+ kraken-mobile setup
136
+
137
+ ### Run tests with settings file
138
+
139
+ kraken-mobile run <apk> --configuration=<kraken_mobile_settings_path>
140
+
141
+ ## Properties file
142
+
143
+ Kraken uses properties files to store sensitive data such as passwords or api keys that should be used in your test cases.
144
+
145
+ ### Generate properties file
146
+
147
+ The properties files should be a manually created json file with the following structure.
148
+
149
+ {
150
+ "@user1": {
151
+ "PASSWORD": "test"
152
+ },
153
+ "@user2": {
154
+ "PASSWORD": "test2"
155
+ }
156
+ }
157
+
158
+ ### Use properties file in your test
159
+
160
+ You can use the specified properties using the following sintax.
161
+
162
+ @user1
163
+ Scenario: As a kjkhdkjds
164
+ Given I wait
165
+ Then I see the text "<PASSWORD>"
166
+
167
+ ### Run tests with settings file
168
+
169
+ kraken-mobile run <apk> --properties=<kraken_mobile_properties_path>
data/bin/kraken-mobile ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), "kraken-mobile-helpers")
4
+ require File.join(File.dirname(__FILE__), "kraken-mobile-setup")
5
+ require File.join(File.dirname(__FILE__), "kraken-mobile-calabash-android")
6
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
7
+ require 'kraken-mobile'
8
+ require 'kraken-mobile/constants'
9
+
10
+ #-------------------------------
11
+ # AGV reader helper
12
+ #-------------------------------
13
+
14
+ def read_specified_runner
15
+ runner_command = ARGV.select { |arg| arg.include?("--runner=") }.first
16
+ if runner_command
17
+ formatted_runner_command = runner_command.strip.downcase
18
+ formatted_runner_command.slice!("--runner=")
19
+ formatted_runner_command
20
+ end
21
+ end
22
+
23
+ def read_specified_protocol
24
+ protocol_command = ARGV.select { |arg| arg.include?("--protocol=") }.first
25
+ if protocol_command
26
+ formatted_protocol_command = protocol_command.strip.downcase
27
+ formatted_protocol_command.slice!("--protocol=")
28
+ if KrakenMobile::Constants::SUPPORTED_PROTOCOLS.include?(formatted_protocol_command)
29
+ formatted_protocol_command
30
+ else # Protocol not supported.
31
+ raise "Specified protocol not supported."
32
+ end
33
+ else # If protocol not specified use file based as default.
34
+ KrakenMobile::Constants::FILE_PROTOCOL
35
+ end
36
+ end
37
+
38
+ def read_configuration
39
+ configuration_command = ARGV.select { |arg| arg.include?("--configuration=") }.first
40
+ if configuration_command
41
+ configuration_command = configuration_command.strip.downcase
42
+ configuration_command.slice!("--configuration=")
43
+ configuration_command
44
+ end
45
+ end
46
+
47
+ def read_properties
48
+ properties_command = ARGV.select { |arg| arg.include?("--properties=") }.first
49
+ if properties_command
50
+ properties_command = properties_command.strip.downcase
51
+ properties_command.slice!("--properties=")
52
+ properties_command
53
+ end
54
+ end
55
+
56
+ #-------------------------------
57
+ # Command reader
58
+ #-------------------------------
59
+
60
+ if ARGV.length == 0
61
+ print_usage
62
+ else
63
+ cmd = ARGV.shift
64
+ case cmd
65
+ when 'version'
66
+ require 'kraken-mobile/version'
67
+ puts KrakenMobile::VERSION
68
+ when 'devices'
69
+ require 'kraken-mobile/helpers/devices_helper/manager'
70
+ print_devices
71
+ when 'setup'
72
+ kraken_setup
73
+ else
74
+ runner_command = read_specified_runner()
75
+ protocol = read_specified_protocol()
76
+ configuration = read_configuration()
77
+ properties = read_properties()
78
+ if runner_command
79
+ case runner_command
80
+ when KrakenMobile::Constants::CALABASH_ANDROID
81
+ require File.join(File.dirname(__FILE__), "kraken-mobile-calabash-android")
82
+ handle_calabash_android(cmd, protocol, configuration, properties)
83
+ else
84
+ raise "Specified runner not supported."
85
+ end
86
+ else # If runner not specified use calabash android as default.
87
+ require File.join(File.dirname(__FILE__), "kraken-mobile-calabash-android")
88
+ handle_calabash_android(cmd, protocol, configuration, properties)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,85 @@
1
+
2
+ #-------------------------------
3
+ # Helpers
4
+ #-------------------------------
5
+ def ensure_apk_is_specified
6
+ if ARGV.empty? || !is_apk_file?(ARGV.first)
7
+ puts "The first parameter must be the path to a valid apk file."
8
+ exit 1
9
+ end
10
+ end
11
+
12
+ def ensure_configuration_is_valid configuration
13
+ if !File.exist?(configuration) || !File.file?(configuration) || !configuration.end_with?(".json")
14
+ puts "The path of the configuration file is not valid."
15
+ exit 1
16
+ end
17
+ end
18
+
19
+ def ensure_properties_is_valid properties
20
+ if !File.exist?(properties) || !File.file?(properties) || !properties.end_with?(".json")
21
+ puts "The path of the properties file is not valid."
22
+ exit 1
23
+ end
24
+ end
25
+
26
+ #-------------------------------
27
+ # Command reader
28
+ #-------------------------------
29
+
30
+ @features_dir = File.join(FileUtils.pwd, "features")
31
+ @support_dir = File.join(@features_dir, "support")
32
+ @source_dir = File.join(File.dirname(__FILE__), '..', 'calabash-android-features-skeleton')
33
+
34
+ def handle_calabash_android cmd, protocol, configuration, properties
35
+ case cmd
36
+ when 'gen'
37
+ require File.join(File.dirname(__FILE__), "kraken-mobile-generate")
38
+ kraken_scaffold()
39
+ when 'resign'
40
+ require 'calabash-android/helpers'
41
+ ensure_apk_is_specified
42
+ puts "Resigning APK with Calabash-Android"
43
+ resign_apk(File.expand_path(ARGV.first))
44
+ when 'monkey'
45
+ require 'kraken-mobile/constants'
46
+ require 'calabash-android/helpers'
47
+ options = {
48
+ runner: KrakenMobile::Constants::MONKEY,
49
+ }
50
+ if configuration
51
+ ensure_configuration_is_valid File.expand_path(configuration)
52
+ options[:config_path] = File.expand_path(configuration)
53
+ else
54
+ ensure_apk_is_specified
55
+ options[:apk_path] = ARGV.first
56
+ end
57
+ # NOT AVAILABLE
58
+ #kraken = KrakenMobile::App.new(options)
59
+ #kraken.run_in_parallel
60
+ when 'run'
61
+ require 'kraken-mobile/constants'
62
+ require 'calabash-android/helpers'
63
+ options = {
64
+ feature_folder: @features_dir,
65
+ runner: KrakenMobile::Constants::CALABASH_ANDROID,
66
+ protocol: protocol
67
+ }
68
+ if configuration
69
+ ensure_configuration_is_valid File.expand_path(configuration)
70
+ options[:config_path] = File.expand_path(configuration)
71
+ else
72
+ ensure_apk_is_specified
73
+ options[:apk_path] = ARGV.first
74
+ end
75
+ if properties
76
+ ensure_properties_is_valid File.expand_path(properties)
77
+ options[:properties_path] = File.expand_path(properties)
78
+ end
79
+ kraken = KrakenMobile::App.new(options)
80
+ kraken.run_in_parallel
81
+ else
82
+ puts "Invalid command '#{cmd}'"
83
+ print_usage
84
+ end
85
+ end
@@ -0,0 +1,19 @@
1
+ def kraken_scaffold
2
+ if File.exists?(@features_dir)
3
+ puts "A features directory already exists. Stopping..."
4
+ exit 1
5
+ end
6
+ msg("Question") do
7
+ puts "I'm about to create a subdirectory called features."
8
+ puts "features will contain all your kraken tests."
9
+ puts "Please hit return to confirm that's what you want."
10
+ end
11
+ exit 2 unless STDIN.gets.chomp == ''
12
+
13
+ FileUtils.cp_r(@source_dir, @features_dir)
14
+
15
+ msg("Info") do
16
+ puts "features subdirectory created. \n"
17
+ end
18
+
19
+ end
@@ -0,0 +1,48 @@
1
+ require "rubygems"
2
+ require 'fileutils'
3
+
4
+ #-------------------------------
5
+ # Helpers
6
+ #-------------------------------
7
+
8
+ def print_usage
9
+ puts <<EOF
10
+ Usage: kraken-mobile <command-name> [parameters] [options]
11
+ <command-name> can be one of
12
+ run <apk>
13
+ runs Cucumber in the current folder with the environment needed.
14
+ version
15
+ prints the gem version.
16
+ devices
17
+ prints the list of devices attached.
18
+ setup
19
+ creates kraken-settings file specifying in what devices the tests are going to be run.
20
+ gen
21
+ generate a features folder structure.
22
+ resign <apk>
23
+ resigns the app with the currently configured keystore.
24
+ EOF
25
+ end
26
+
27
+ def print_devices
28
+ runner = KrakenMobile::Constants::CALABASH_ANDROID
29
+ device_manager = KrakenMobile::DevicesHelper::Manager.new({runner: runner})
30
+ puts "List of devices attached"
31
+ device_manager.connected_devices.each_with_index do |device, index|
32
+ puts "user#{index+1} - #{device.id} - #{device.model}"
33
+ end
34
+ end
35
+
36
+ def is_apk_file?(file_path)
37
+ file_path.end_with? ".apk" and File.exist? file_path
38
+ end
39
+
40
+ def relative_to_full_path(file_path)
41
+ File.expand_path(file_path)
42
+ end
43
+
44
+ def msg(title, &block)
45
+ puts "\n" + "-"*10 + title + "-"*10
46
+ block.call
47
+ puts "-"*10 + "-------" + "-"*10 + "\n"
48
+ end
@@ -0,0 +1,50 @@
1
+ require "io/console"
2
+ require "tty-prompt"
3
+
4
+ def kraken_setup
5
+ prompt = TTY::Prompt.new
6
+ runner = KrakenMobile::Constants::CALABASH_ANDROID
7
+ device_manager = KrakenMobile::DevicesHelper::Manager.new({runner: runner})
8
+ @settings = []
9
+
10
+ devices_connected = device_manager.connected_devices
11
+ if devices_connected.count <= 0
12
+ puts "No devices connected"
13
+ exit 1
14
+ end
15
+ number_of_devices = -1
16
+ while(number_of_devices <= 0 || number_of_devices > devices_connected.count)
17
+ number_of_devices = prompt.ask("How many devices do you want to use?", convert: :int)
18
+ prompt.error("Number of devices can't be less than 1.") if number_of_devices <= 0
19
+ prompt.error("You only have #{devices_connected.count} devices connected.") if number_of_devices > devices_connected.count
20
+ end
21
+
22
+ devices_connected_id = devices_connected.map { |device| device.id }
23
+ for i in 0...number_of_devices
24
+ selected_device_id = prompt.select("Choose your user #{i+1}", devices_connected_id)
25
+ valid_apk = false
26
+ entered_apk_path = nil
27
+
28
+ while(!valid_apk)
29
+ entered_text = prompt.ask("What APK user #{i+1} is going to run?", required: true)
30
+ entered_apk_path = File.expand_path(entered_text)
31
+ valid_apk = is_apk_file?(File.expand_path(entered_apk_path))
32
+ prompt.error("APK path is not valid.") unless valid_apk
33
+ end
34
+
35
+ device = devices_connected.select { |d| d.id == selected_device_id }.first
36
+ @settings[i] = {
37
+ id: device.id,
38
+ model: device.model,
39
+ config: {
40
+ apk_path: entered_apk_path
41
+ }
42
+ }
43
+ devices_connected_id.delete(selected_device_id)
44
+ end
45
+
46
+ open('kraken_mobile_settings.json', 'w') do |f|
47
+ f.puts @settings.to_json
48
+ end
49
+ puts "Saved your settings to .kraken_mobile_settings. You can edit the settings manually or run this setup script again"
50
+ end