kraken-mobile 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +45 -11
- data/bin/kraken-mobile +55 -67
- data/bin/kraken_mobile_calabash_android.rb +39 -0
- data/bin/kraken_mobile_helpers.rb +91 -0
- data/bin/kraken_mobile_setup.rb +134 -0
- data/calabash-android-features-skeleton/step_definitions/mobile_steps.rb +1 -0
- data/calabash-android-features-skeleton/support/app_installation_hooks.rb +2 -0
- data/calabash-android-features-skeleton/support/app_life_cycle_hooks.rb +3 -4
- data/calabash-android-features-skeleton/support/env.rb +1 -1
- data/calabash-android-features-skeleton/web/step_definitions/web_steps.rb +3 -0
- data/calabash-android-features-skeleton/web/support/app_life_cycle_hooks.rb +15 -0
- data/lib/kraken-mobile/device_process.rb +94 -0
- data/lib/kraken-mobile/helpers/devices_helper/adb_helper.rb +100 -105
- data/lib/kraken-mobile/helpers/kraken_faker.rb +107 -0
- data/lib/kraken-mobile/hooks/mobile_kraken_hooks.rb +15 -0
- data/lib/kraken-mobile/hooks/mobile_operations.rb +36 -0
- data/lib/kraken-mobile/hooks/web_operations.rb +33 -0
- data/lib/kraken-mobile/mobile/adb.rb +66 -0
- data/lib/kraken-mobile/mobile/android_commands.rb +43 -0
- data/lib/kraken-mobile/mobile/mobile_process.rb +82 -0
- data/lib/kraken-mobile/models/android_device.rb +121 -0
- data/lib/kraken-mobile/models/device.rb +113 -31
- data/lib/kraken-mobile/models/feature_file.rb +108 -0
- data/lib/kraken-mobile/models/feature_scenario.rb +24 -0
- data/lib/kraken-mobile/models/web_device.rb +86 -0
- data/lib/kraken-mobile/monkeys/mobile/android_monkey.rb +30 -0
- data/lib/kraken-mobile/monkeys/mobile/kraken_android_monkey.rb +54 -0
- data/lib/kraken-mobile/runners/calabash/android/steps/communication_steps.rb +15 -0
- data/lib/kraken-mobile/steps/general_steps.rb +82 -0
- data/lib/kraken-mobile/steps/mobile/kraken_steps.rb +72 -0
- data/lib/kraken-mobile/steps/web/kraken_steps.rb +81 -0
- data/lib/kraken-mobile/test_scenario.rb +193 -0
- data/lib/kraken-mobile/utils/feature_reader.rb +17 -0
- data/lib/kraken-mobile/utils/k.rb +67 -0
- data/lib/kraken-mobile/utils/mobile_cucumber.rb +2 -0
- data/lib/kraken-mobile/utils/reporter.rb +453 -0
- data/lib/kraken-mobile/version.rb +2 -2
- data/lib/kraken-mobile/web/web_process.rb +39 -0
- data/lib/kraken_mobile.rb +77 -0
- data/reporter/index.html.erb +6 -6
- metadata +89 -9
- data/bin/kraken-mobile-calabash-android.rb +0 -85
- data/bin/kraken-mobile-generate.rb +0 -19
- data/bin/kraken-mobile-helpers.rb +0 -48
- data/bin/kraken-mobile-setup.rb +0 -50
- data/calabash-android-features-skeleton/step_definitions/kraken_steps.rb +0 -1
- data/lib/kraken-mobile.rb +0 -29
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'gherkin/parser'
|
2
|
+
require 'gherkin/pickles/compiler'
|
3
|
+
require 'kraken-mobile/models/feature_scenario'
|
4
|
+
|
5
|
+
class FeatureFile
|
6
|
+
#-------------------------------
|
7
|
+
# Fields
|
8
|
+
#-------------------------------
|
9
|
+
attr_accessor :file_path
|
10
|
+
attr_accessor :scenarios
|
11
|
+
|
12
|
+
#-------------------------------
|
13
|
+
# Constructos
|
14
|
+
#-------------------------------
|
15
|
+
def initialize(file_path:)
|
16
|
+
@file_path = file_path
|
17
|
+
@scenarios = []
|
18
|
+
|
19
|
+
read_content
|
20
|
+
end
|
21
|
+
|
22
|
+
#-------------------------------
|
23
|
+
# Helpers
|
24
|
+
#-------------------------------
|
25
|
+
def number_of_required_mobile_devices
|
26
|
+
all_tags = @scenarios.map(&:tags).flatten.uniq
|
27
|
+
mobile_tagged_count = all_tags.select { |tag| tag == '@mobile' }.count
|
28
|
+
empty_tagged_scenarios = @scenarios.select do |scenario|
|
29
|
+
!scenario.tags.include?('@mobile') &&
|
30
|
+
!scenario.tags.include?('@web')
|
31
|
+
end
|
32
|
+
mobile_tagged_count + empty_tagged_scenarios.count
|
33
|
+
end
|
34
|
+
|
35
|
+
def number_of_required_web_devices
|
36
|
+
all_tags = @scenarios.map(&:tags).flatten.uniq
|
37
|
+
all_tags.select { |tag| tag == '@web' }.count
|
38
|
+
end
|
39
|
+
|
40
|
+
def number_of_required_devices
|
41
|
+
all_tags = @scenarios.map(&:tags).flatten.uniq
|
42
|
+
all_tags.select { |tag| tag.start_with?('@user') }.count
|
43
|
+
end
|
44
|
+
|
45
|
+
def tags_for_user_id(user_id)
|
46
|
+
user_tag = "@user#{user_id}"
|
47
|
+
user_scenario = @scenarios.select do |scenario|
|
48
|
+
scenario.tags.include?(user_tag)
|
49
|
+
end.first
|
50
|
+
return [] if user_scenario.nil? || user_scenario.tags.nil?
|
51
|
+
|
52
|
+
user_scenario.tags.reject { |tag| tag == user_tag }
|
53
|
+
end
|
54
|
+
|
55
|
+
def right_syntax?
|
56
|
+
all_scenarios_have_a_user_tag? &&
|
57
|
+
only_one_user_tag_for_each_scenario? &&
|
58
|
+
!duplicate_tags_for_a_user?
|
59
|
+
end
|
60
|
+
|
61
|
+
def duplicate_tags_for_a_user?
|
62
|
+
taken_user_tags = {}
|
63
|
+
scenarios.each do |scenario|
|
64
|
+
user_tag = scenario.tags.select do |tag|
|
65
|
+
tag.start_with?('@user')
|
66
|
+
end.first
|
67
|
+
return true unless taken_user_tags[user_tag].nil?
|
68
|
+
|
69
|
+
taken_user_tags[user_tag] = user_tag
|
70
|
+
end
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def only_one_user_tag_for_each_scenario?
|
75
|
+
scenarios.each do |scenario|
|
76
|
+
user_tags = scenario.tags.select do |tag|
|
77
|
+
tag.start_with?('@user')
|
78
|
+
end
|
79
|
+
return false if user_tags.count != 1
|
80
|
+
end
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def all_scenarios_have_a_user_tag?
|
85
|
+
scenarios.each do |scenario|
|
86
|
+
user_tag = scenario.tags.select do |tag|
|
87
|
+
tag.start_with?('@user')
|
88
|
+
end.first
|
89
|
+
return false if user_tag.nil?
|
90
|
+
end
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def read_content
|
97
|
+
parser = Gherkin::Parser.new
|
98
|
+
file_content = File.open(file_path).read
|
99
|
+
gherkin_document = parser.parse(file_content)
|
100
|
+
pickles = Gherkin::Pickles::Compiler.new.compile(gherkin_document)
|
101
|
+
pickles.each do |scenario|
|
102
|
+
scenarios << FeatureScenario.new(
|
103
|
+
name: scenario[:name],
|
104
|
+
tags: scenario[:tags]
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class FeatureScenario
|
2
|
+
#-------------------------------
|
3
|
+
# Fields
|
4
|
+
#-------------------------------
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :tags
|
7
|
+
|
8
|
+
#-------------------------------
|
9
|
+
# Constructos
|
10
|
+
#-------------------------------
|
11
|
+
def initialize(name:, tags:)
|
12
|
+
@name = name
|
13
|
+
@tags = format_tags(tags) || []
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
#-------------------------------
|
19
|
+
# Helpers
|
20
|
+
#-------------------------------
|
21
|
+
def format_tags(tags)
|
22
|
+
tags.map { |tag| tag[:name] }
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'kraken-mobile/models/device'
|
2
|
+
|
3
|
+
class WebDevice < Device
|
4
|
+
#-------------------------------
|
5
|
+
# Signaling
|
6
|
+
#-------------------------------
|
7
|
+
def create_inbox
|
8
|
+
File.open(inbox_file_path, 'w')
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete_inbox
|
12
|
+
return unless File.exist? inbox_file_path
|
13
|
+
|
14
|
+
File.delete(inbox_file_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def write_signal(signal)
|
18
|
+
File.open(inbox_file_path, 'a') do |file|
|
19
|
+
file.puts(signal)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_signal(signal, timeout = K::DEFAULT_TIMEOUT_SECONDS)
|
24
|
+
Timeout.timeout(timeout, RuntimeError) do
|
25
|
+
sleep(1) until inbox_last_signal == signal
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#-------------------------------
|
30
|
+
# More interface methods
|
31
|
+
#-------------------------------
|
32
|
+
def connected?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def orientation
|
37
|
+
K::WEB_PORTRAIT
|
38
|
+
end
|
39
|
+
|
40
|
+
def screen_size
|
41
|
+
height = 0
|
42
|
+
width = 0
|
43
|
+
|
44
|
+
[height, width]
|
45
|
+
end
|
46
|
+
|
47
|
+
def sdk_version
|
48
|
+
1.0 # Default
|
49
|
+
end
|
50
|
+
|
51
|
+
def type
|
52
|
+
K::WEB_DEVICE
|
53
|
+
end
|
54
|
+
|
55
|
+
#-------------------------------
|
56
|
+
# Random testing
|
57
|
+
#-------------------------------
|
58
|
+
def run_monkey_with_number_of_events(number_of_events)
|
59
|
+
number_of_events # TODO, implement
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_kraken_monkey_with_number_of_events(number_of_events)
|
63
|
+
number_of_events # TODO, implement
|
64
|
+
end
|
65
|
+
|
66
|
+
#-------------------------------
|
67
|
+
# Helpers
|
68
|
+
#-------------------------------
|
69
|
+
def self.factory_create
|
70
|
+
WebDevice.new(
|
71
|
+
id: SecureRandom.hex(10),
|
72
|
+
model: 'Web'
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def inbox_file_path
|
79
|
+
".#{@id}_#{K::INBOX_FILE_NAME}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def inbox_last_signal
|
83
|
+
lines = File.open(inbox_file_path).to_a
|
84
|
+
lines.last&.strip
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'calabash-android/environment_helpers.rb'
|
2
|
+
require 'calabash-android/monkey_helpers'
|
3
|
+
require 'calabash-android/operations'
|
4
|
+
|
5
|
+
module AndroidMonkey
|
6
|
+
include Calabash::Android::Operations
|
7
|
+
include Calabash::Android::MonkeyHelpers
|
8
|
+
|
9
|
+
def execute_monkey(number_of_events)
|
10
|
+
height, width = screen_size
|
11
|
+
start_monkey
|
12
|
+
|
13
|
+
number_of_events.times do |_i|
|
14
|
+
monkey_touch(
|
15
|
+
K::CALABASH_MONKEY_ACTIONS.sample,
|
16
|
+
rand(5..(width - 5)),
|
17
|
+
rand(5..(height - 5))
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
kill_existing_monkey_processes
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Override calabash super adb_command method
|
27
|
+
def adb_command
|
28
|
+
calabash_default_device.adb_command
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module KrakenAndroidMonkey
|
2
|
+
def execute_kraken_monkey(number_of_events)
|
3
|
+
number_of_events.times do |_i|
|
4
|
+
execute_random_action
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute_random_action
|
9
|
+
Timeout.timeout(K::MONKEY_DEFAULT_TIMEOUT, RuntimeError) do
|
10
|
+
begin
|
11
|
+
arr = [
|
12
|
+
method(:random_click), method(:insert_random_text)
|
13
|
+
]
|
14
|
+
arr.sample.call
|
15
|
+
rescue StandardError => _e
|
16
|
+
puts 'ERROR: Kraken monkey couldn\'t perfom action'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Actions
|
22
|
+
private
|
23
|
+
|
24
|
+
def random_click
|
25
|
+
elements = query('*')
|
26
|
+
return if elements.nil?
|
27
|
+
return if elements.none?
|
28
|
+
|
29
|
+
element = elements.sample
|
30
|
+
return if element['rect'].nil?
|
31
|
+
|
32
|
+
x = element['rect']['x']
|
33
|
+
y = element['rect']['y']
|
34
|
+
perform_action('touch_coordinate', x, y)
|
35
|
+
end
|
36
|
+
|
37
|
+
def insert_random_text
|
38
|
+
inputs = query('android.support.v7.widget.AppCompatEditText')
|
39
|
+
return if inputs.nil?
|
40
|
+
return if inputs.none?
|
41
|
+
|
42
|
+
input = inputs.sample
|
43
|
+
return if input['rect'].nil?
|
44
|
+
|
45
|
+
x = input['rect']['x']
|
46
|
+
y = input['rect']['y']
|
47
|
+
perform_action('touch_coordinate', x, y)
|
48
|
+
enter_text SecureRandom.hex
|
49
|
+
end
|
50
|
+
|
51
|
+
def input_texts
|
52
|
+
query('android.support.v7.widget.AppCompatEditText')
|
53
|
+
end
|
54
|
+
end
|
@@ -18,6 +18,21 @@ ParameterType(
|
|
18
18
|
}
|
19
19
|
)
|
20
20
|
|
21
|
+
ParameterType(
|
22
|
+
name: 'property',
|
23
|
+
regexp: /[^\"]*/,
|
24
|
+
type: String,
|
25
|
+
transformer: ->(s) {
|
26
|
+
channel = @scenario_tags.grep(/@user/).first
|
27
|
+
if ENV["PROPERTIES_PATH"] && channel && s.start_with?("<") && s.end_with?(">")
|
28
|
+
s.slice!('$')
|
29
|
+
return properties[channel][s]
|
30
|
+
else
|
31
|
+
return s
|
32
|
+
end
|
33
|
+
}
|
34
|
+
)
|
35
|
+
|
21
36
|
Then /^I wait for a signal containing "([^\"]*)"$/ do |string|
|
22
37
|
channel = @scenario_tags.grep(/@user/).first
|
23
38
|
readSignal(channel, string, KrakenMobile::Constants::DEFAULT_TIMEOUT)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'kraken-mobile/helpers/kraken_faker'
|
2
|
+
|
3
|
+
ParameterType(
|
4
|
+
name: 'property',
|
5
|
+
regexp: /[^\"]*/,
|
6
|
+
type: String,
|
7
|
+
transformer: lambda do |string|
|
8
|
+
if string_is_a_property?(string)
|
9
|
+
string.slice!('<')
|
10
|
+
string.slice!('>')
|
11
|
+
handle_property(string)
|
12
|
+
elsif string_is_a_faker_reuse?(string)
|
13
|
+
handle_faker_reuse(string)
|
14
|
+
elsif string_is_a_faker?(string)
|
15
|
+
handle_faker(string)
|
16
|
+
else
|
17
|
+
return string
|
18
|
+
end
|
19
|
+
end
|
20
|
+
)
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def current_process_id
|
25
|
+
tag_process_id = @scenario_tags.grep(/@user/).first
|
26
|
+
process_id = tag_process_id.delete_prefix('@user')
|
27
|
+
return 'ERROR: User not foud for scenario' if process_id.nil?
|
28
|
+
|
29
|
+
process_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def string_is_a_property?(string)
|
33
|
+
string.start_with?('<') &&
|
34
|
+
string.end_with?('>')
|
35
|
+
end
|
36
|
+
|
37
|
+
def string_is_a_faker?(string)
|
38
|
+
string.start_with?('$')
|
39
|
+
end
|
40
|
+
|
41
|
+
def string_is_a_faker_reuse?(string)
|
42
|
+
string.start_with?('$$')
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_property(property)
|
46
|
+
properties = all_user_properties_as_json
|
47
|
+
process_id = current_process_id
|
48
|
+
user_id = "@user#{process_id}"
|
49
|
+
|
50
|
+
if !properties[user_id] || !properties[user_id][property]
|
51
|
+
raise "Property <#{property}> not found for @user#{current_process_id}"
|
52
|
+
end
|
53
|
+
|
54
|
+
properties[user_id][property]
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_user_properties_as_json
|
58
|
+
raise 'ERROR: No properties file found' if ENV[K::PROPERTIES_PATH].nil?
|
59
|
+
|
60
|
+
properties_absolute_path = File.expand_path(ENV[K::PROPERTIES_PATH])
|
61
|
+
raise 'ERROR: Properties file not found' unless File.file?(
|
62
|
+
properties_absolute_path
|
63
|
+
)
|
64
|
+
|
65
|
+
file = open(properties_absolute_path)
|
66
|
+
content = file.read
|
67
|
+
JSON.parse(content)
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_faker(key)
|
71
|
+
faker = KrakenFaker.new(process_id: current_process_id)
|
72
|
+
faker.generate_value_for_key(
|
73
|
+
key: key
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_faker_reuse(key)
|
78
|
+
faker = KrakenFaker.new(process_id: current_process_id)
|
79
|
+
faker.reuse_value_for_key(
|
80
|
+
key: key
|
81
|
+
)
|
82
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'calabash-android/calabash_steps'
|
2
|
+
require 'kraken-mobile/utils/K'
|
3
|
+
require 'kraken-mobile/models/android_device'
|
4
|
+
require 'kraken-mobile/steps/general_steps'
|
5
|
+
|
6
|
+
Then(
|
7
|
+
/^I send a signal to user (\d+) containing "([^\"]*)"$/
|
8
|
+
) do |process_id, signal|
|
9
|
+
device = Device.find_by_process_id(process_id)
|
10
|
+
raise 'ERROR: Device not found' if device.nil?
|
11
|
+
if process_id.to_s == current_process_id.to_s
|
12
|
+
raise 'ERROR: Can\'t send signal to same device'
|
13
|
+
end
|
14
|
+
|
15
|
+
device.write_signal(signal)
|
16
|
+
end
|
17
|
+
|
18
|
+
Then(/^I wait for a signal containing "([^\"]*)"$/) do |signal|
|
19
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.nil?
|
20
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.grep(/@user/).none?
|
21
|
+
|
22
|
+
device = Device.find_by_process_id(current_process_id)
|
23
|
+
raise 'ERROR: Device not found' if device.nil?
|
24
|
+
|
25
|
+
device.read_signal(signal)
|
26
|
+
end
|
27
|
+
|
28
|
+
Then(
|
29
|
+
/^I wait for a signal containing "([^\"]*)" for (\d+) seconds$/
|
30
|
+
) do |signal, seconds|
|
31
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.nil?
|
32
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.grep(/@user/).none?
|
33
|
+
|
34
|
+
device = Device.find_by_process_id(current_process_id)
|
35
|
+
raise 'ERROR: Device not found' if device.nil?
|
36
|
+
|
37
|
+
device.read_signal(signal, seconds)
|
38
|
+
end
|
39
|
+
|
40
|
+
Then(/^I start a monkey with (\d+) events$/) do |number_of_events|
|
41
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.nil?
|
42
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.grep(/@user/).none?
|
43
|
+
|
44
|
+
device = Device.find_by_process_id(current_process_id)
|
45
|
+
raise 'ERROR: Device not found' if device.nil?
|
46
|
+
|
47
|
+
device.run_monkey_with_number_of_events(number_of_events)
|
48
|
+
end
|
49
|
+
|
50
|
+
Then(/^I start kraken monkey with (\d+) events$/) do |number_of_events|
|
51
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.nil?
|
52
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.grep(/@user/).none?
|
53
|
+
|
54
|
+
device = Device.find_by_process_id(current_process_id)
|
55
|
+
raise 'ERROR: Device not found' if device.nil?
|
56
|
+
|
57
|
+
device.run_kraken_monkey_with_number_of_events(number_of_events)
|
58
|
+
end
|
59
|
+
|
60
|
+
Then(/^I save device snapshot in file with path "([^\"]*)"$/) do |file_path|
|
61
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.nil?
|
62
|
+
raise 'ERROR: Invalid scenario tag' if @scenario_tags.grep(/@user/).none?
|
63
|
+
|
64
|
+
device = Device.find_by_process_id(current_process_id)
|
65
|
+
raise 'ERROR: Device not found' if device.nil?
|
66
|
+
|
67
|
+
device.save_snapshot_in_file_path(file_path)
|
68
|
+
end
|
69
|
+
|
70
|
+
Then(/^I enter text "([^\"]*)"$/) do |text|
|
71
|
+
keyboard_enter_text(text, {})
|
72
|
+
end
|