tabbyx 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/bin/console +14 -0
  3. data/bin/setup +8 -0
  4. data/lib/tabbyx.rb +29 -0
  5. data/lib/tabbyx/calabash_android.rb +11 -0
  6. data/lib/tabbyx/calabash_ios.rb +4 -0
  7. data/lib/tabbyx/core/base.rb +54 -0
  8. data/lib/tabbyx/core/config.rb +36 -0
  9. data/lib/tabbyx/core/initialize.rb +35 -0
  10. data/lib/tabbyx/fixtures/constants.rb +21 -0
  11. data/lib/tabbyx/fixtures/global.rb +39 -0
  12. data/lib/tabbyx/helpers/csv_helper.rb +65 -0
  13. data/lib/tabbyx/helpers/debug_helper.rb +37 -0
  14. data/lib/tabbyx/helpers/excel_helper.rb +79 -0
  15. data/lib/tabbyx/helpers/http_batch_handler.rb +50 -0
  16. data/lib/tabbyx/helpers/http_helper.rb +46 -0
  17. data/lib/tabbyx/helpers/minitest_helper.rb +48 -0
  18. data/lib/tabbyx/helpers/screenshot_helpers.rb +128 -0
  19. data/lib/tabbyx/helpers/txt_helper.rb +35 -0
  20. data/lib/tabbyx/steps_android/assert_steps.rb +31 -0
  21. data/lib/tabbyx/steps_android/check_box_steps.rb +3 -0
  22. data/lib/tabbyx/steps_android/context_menu_steps.rb +17 -0
  23. data/lib/tabbyx/steps_android/date_picker_steps.rb +8 -0
  24. data/lib/tabbyx/steps_android/enter_text_steps.rb +23 -0
  25. data/lib/tabbyx/steps_android/location_steps.rb +7 -0
  26. data/lib/tabbyx/steps_android/navigation_steps.rb +47 -0
  27. data/lib/tabbyx/steps_android/press_button_steps.rb +39 -0
  28. data/lib/tabbyx/steps_android/progress_steps.rb +51 -0
  29. data/lib/tabbyx/steps_android/search_steps.rb +7 -0
  30. data/lib/tabbyx/steps_android/spinner_steps.rb +11 -0
  31. data/lib/tabbyx/steps_ios/assertions.rb +79 -0
  32. data/lib/tabbyx/steps_ios/date_picker.rb +39 -0
  33. data/lib/tabbyx/steps_ios/operations.rb +187 -0
  34. data/lib/tabbyx/steps_ios/wait.rb +48 -0
  35. data/lib/tabbyx/utils/adb_devices.rb +248 -0
  36. data/lib/tabbyx/utils/adb_screenshot.rb +22 -0
  37. data/lib/tabbyx/utils/code_coverage.rb +6 -0
  38. data/lib/tabbyx/utils/shell.rb +32 -0
  39. data/lib/tabbyx/version.rb +3 -0
  40. metadata +286 -0
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+ require 'excel_helper'
3
+ require 'http_helper'
4
+
5
+ =begin
6
+ 使用方法:
7
+ 1. 将API的url,参数,action等按照模板 inputs/api_testcases.xlsx 填写
8
+ TESTCASE ACTION URL PARAMS
9
+ 2. 运行测试,接口请求返回的结果会填回表格
10
+ example:
11
+ RunTestCases.new("api_testcases.xlsx",0).run_test_cases
12
+ 3. 运行完毕后打开excel文件查看返回数据
13
+ =end
14
+ class BatchGetResponse
15
+ attr_accessor :testcases
16
+
17
+ def initialize(filename,worksheet=0)
18
+ @testcases = ExcelHelper.read_from_excel(filename,worksheet)
19
+ @filename = filename
20
+ @worksheet = worksheet
21
+ end
22
+
23
+ def run_test_cases
24
+ testresults = []
25
+ @testcases.each do |testcase|
26
+ testresult = []
27
+ unless testcase[1].nil?
28
+ (0..3).each { |i| testresult[i] = testcase[i] }
29
+ action = testcase[1].strip
30
+ url = testcase[2].strip
31
+ params = testcase[3]
32
+ end
33
+
34
+ case action
35
+ when 'GET'
36
+ res = HTTPHelper.new.get_response(HOST+url,params)
37
+ testresult << res << res.code << res["Data"]
38
+ when 'POST'
39
+ res = HTTPHelper.new.post_response(HOST+url,params)
40
+ testresult << res << res.code << res["Data"]
41
+ when 'ACTION'
42
+ testresult << "RESPONSE" << "RESPONSE CODE" << "DATA"
43
+ end
44
+
45
+ testresults.push testresult
46
+ end
47
+ ExcelHelper.write_dictionary_to_excel(testresults,"testresult1.xlsx",@worksheet) unless testresults.nil?
48
+ end
49
+
50
+ end
@@ -0,0 +1,46 @@
1
+ require 'httparty'
2
+ require 'tabbyx/fixtures/global'
3
+
4
+ module HTTPHelper
5
+ @header =
6
+ {
7
+ 'Content-Type' => 'application/json',
8
+ 'Accept' => 'application/json',
9
+ 'User-Agent' => USER_AGENT,
10
+ 'Cookie' => COOKIE
11
+ }.to_hash
12
+
13
+ def self.post_response(requestUrl,params=nil,serviceRequest=nil,query=nil)
14
+ @body =
15
+ {
16
+ :serviceRequest => serviceRequest
17
+ }
18
+
19
+ params ? request = requestUrl+'?'+params : request = requestUrl
20
+ puts "请求: " + request
21
+
22
+ if serviceRequest.nil?
23
+ response = HTTParty.post(request, :query => query, :headers => @header)
24
+ else
25
+ response = HTTParty.post(request, :body => @body, :headers => @header)
26
+ end
27
+
28
+ response
29
+ end
30
+
31
+ def self.get_response(requestUrl, params=nil, query=nil)
32
+ params ? request = requestUrl+'?'+params : request = requestUrl
33
+ query ? q = query.to_hash : q = query
34
+ response = HTTParty.get(request, :query => q, :headers => @header)
35
+ response
36
+ end
37
+
38
+ def self.response_code(response)
39
+ response.code
40
+ end
41
+
42
+ def self.response_data(response)
43
+ response["Data"] || response["data"]
44
+ end
45
+
46
+ end
@@ -0,0 +1,48 @@
1
+ # require this file if you want to use minitest-reporter
2
+
3
+ require 'minitest/reporters'
4
+
5
+ # test report for rake testing(API & unit testing)
6
+ module Minitest
7
+ module Reporters
8
+ class AwesomeReporter < HtmlReporter
9
+ GRAY = '0;36'
10
+ GREEN = '1;32'
11
+ RED = '1;31'
12
+
13
+ def initialize(options = {})
14
+ super
15
+ @slow_threshold = options.fetch(:slow_threshold, nil)
16
+ end
17
+
18
+ def record_pass(test)
19
+ if @slow_threshold.nil? || test.time <= @slow_threshold
20
+ super
21
+ else
22
+ gray('O')
23
+ end
24
+ end
25
+
26
+ def color_up(string, color)
27
+ color? ? "\e\[#{ color }m#{ string }#{ ANSI::Code::ENDCODE }" : string
28
+ end
29
+
30
+ def red(string)
31
+ color_up(string, RED)
32
+ end
33
+
34
+ def green(string)
35
+ color_up(string, GREEN)
36
+ end
37
+
38
+ def gray(string)
39
+ color_up(string, GRAY)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ reporter_options = { color: true, slow_count: 5, reports_dir:'reports'}
47
+ report = Minitest::Reporters::AwesomeReporter.new(reporter_options)
48
+ Minitest::Reporters.use! [report]
@@ -0,0 +1,128 @@
1
+ # This requires ImageMagick to be installed
2
+
3
+ def screenshot_dirs
4
+ base_dir = screenshot_base_dir
5
+ orig_dir = base_dir + 'orig'
6
+ check_dir = base_dir + 'check'
7
+ diff_dir = base_dir + 'diff'
8
+
9
+ # Create directories if they don't exist
10
+ FileUtils.mkdir_p(orig_dir)
11
+ FileUtils.mkdir_p(check_dir)
12
+ FileUtils.mkdir_p(diff_dir)
13
+
14
+ {:orig_dir => orig_dir, :check_dir => check_dir, :diff_dir => diff_dir}
15
+ end
16
+
17
+ def remember_screen_as(screen)
18
+ screen.tr!(" ", "_")
19
+ dirs = screenshot_dirs
20
+ orig_filename = "#{dirs[:orig_dir]}/" + "#{screen}.png"
21
+ capture_filename = screenshot({:prefix => "#{dirs[:orig_dir]}/", :name => "#{screen}.png"})
22
+ puts orig_filename
23
+ puts capture_filename
24
+ if (orig_filename != capture_filename)
25
+ FileUtils.mv(capture_filename,orig_filename) # calabash might add a counter to the file
26
+ end
27
+ end
28
+
29
+ def compare_screen(screen)
30
+ screen.tr!(" ", "_")
31
+ dirs = screenshot_dirs
32
+
33
+ screenshot_filename = screenshot({:prefix => "#{dirs[:check_dir]}/", :name => "#{screen}.png"})
34
+ check_filename = "#{dirs[:check_dir]}/#{screen}.png"
35
+ check_rotated_filename = "#{dirs[:check_dir]}/#{screen}_rotated.png"
36
+ orig_filename = "#{dirs[:orig_dir]}/#{screen}.png"
37
+ diff_filename = "#{dirs[:diff_dir]}/#{screen}.png"
38
+ diff_rotated_filename = "#{dirs[:diff_dir]}/#{screen}_rotated.png"
39
+
40
+ # Moving files is instant, sleep 1 sec is needed to ensure that the movement is done before compaing two images.
41
+ FileUtils.mv(screenshot_filename,check_filename) # discard number, only keep one
42
+ sleep 1
43
+
44
+ if (File.exists? orig_filename)
45
+ # If the two images don't have the same width and height, ImageMagic's compare method will throw an exception
46
+ # get the original image's dimension
47
+ orig_image_size = (%x[identify -format "%[fx:w]x%[fx:h]" "#{orig_filename}"]).chomp # might be 1536 × 2048, 768x1024 or return error
48
+
49
+ # get the dimension of the image that is to be checked
50
+ check_image_size = (%x[identify -format "%[fx:w]x%[fx:h]" "#{check_filename}"]).chomp
51
+
52
+ # occasionally identify method isn't reliable, you will get 0x0 above, the image's width and height should have 3-4 digits
53
+ if /^(\d{3,4})x(\d{3,4})$/.match(orig_image_size).nil?
54
+ fail(msg="Cannot get image size, please replace the original image")
55
+ end
56
+
57
+ if /^(\d{3,4})x(\d{3,4})$/.match(check_image_size).nil?
58
+ fail(msg="the original screenshot's info isn't readable to ImageMagic, please check.")
59
+ end
60
+
61
+ # convert the check_image to the same size of the orig_image if necessary, so they are comparable
62
+ %x[convert "#{check_filename}" -resize #{orig_image_size} "#{check_filename}"] unless orig_image_size == check_image_size
63
+ sleep 2
64
+ # Create a rotated version of the screenshot
65
+ # shell_res = %x[convert "#{check_filename}" -rotate 180 #{check_rotated_filename}]
66
+
67
+ # Compare pixel-by-pixel
68
+ # -fuzz %1 is too strict, change it to 5%
69
+ diff = (%x[compare -metric AE -fuzz 5% "#{orig_filename}" "#{check_filename}" "#{diff_filename}" 2>&1]).chomp
70
+ diff_rotated = (%x[compare -metric AE -fuzz 5% "#{orig_filename}" "#{check_rotated_filename}" "#{diff_rotated_filename}" 2>&1]).chomp
71
+ if (!diff.match(/^\d.*$/))
72
+ fail(msg="Error comparing pictures. '#{diff}'")
73
+ end
74
+
75
+ # Use rotated file if it has a better match
76
+ if (diff.to_f > diff_rotated.to_f)
77
+ log "Using rotated screenshot instead, since it provides a better match"
78
+ FileUtils.cp(check_rotated_filename,check_filename)
79
+ FileUtils.cp(diff_rotated_filename,diff_filename)
80
+ diff = diff_rotated
81
+ end
82
+ FileUtils.rm check_rotated_filename
83
+ FileUtils.rm diff_rotated_filename
84
+
85
+ pixel_count = (%x[convert "#{orig_filename}" -format "%[fx:w*h]" info:]).chomp # occasionally unreliable
86
+ percent_diff = 100*diff.to_f/pixel_count.to_f
87
+ puts "Screenshots diff: #{percent_diff}"
88
+
89
+ # Remove diff if no difference
90
+ FileUtils.rm diff_filename if (percent_diff == 0)
91
+
92
+ else
93
+ # No original file. Use the new screenshot. Keep the one in "check".
94
+ FileUtils.cp(check_filename,orig_filename)
95
+ log "No original screenshot. New has been created at #{orig_filename}. Please check."
96
+ percent_diff = 0
97
+ end
98
+
99
+ {
100
+ :check_filename => check_filename,
101
+ :orig_filename => orig_filename,
102
+ :diff_filename => diff_filename,
103
+ :percent_diff => percent_diff
104
+ }
105
+ end
106
+
107
+ def screen_match?(screen, x = 100, invert = false, always_clean = false)
108
+ res = compare_screen(screen)
109
+
110
+ FileUtils.rm res[:check_filename] if always_clean
111
+
112
+ return false if (!invert && (100-res[:percent_diff]) < x.to_f)
113
+ return false if (invert && (100-res[:percent_diff]) >= x.to_f)
114
+
115
+ # Remove check file if everything is ok
116
+ FileUtils.rm res[:check_filename] if !always_clean
117
+
118
+ true
119
+ end
120
+
121
+ def screen_not_match?(screen, x = 100) ; screen_match?(screen, x, true) end
122
+
123
+ def assert_screen_match(screen,x = 100,invert = false)
124
+ match = screen_match?(screen,x,invert)
125
+ fail(msg="Screenshots differ too much.") if !match && !invert
126
+ fail(msg="Screenshots differ too little.") if !match && invert
127
+ true
128
+ end
@@ -0,0 +1,35 @@
1
+ require 'tabbyx/core/base'
2
+
3
+ module TXThelper
4
+
5
+ # example:
6
+ # TXThelper.read_from_text('api_testcases.csv')
7
+
8
+ def self.read_from_text(filename)
9
+ file = Base.file_exists?(filename)
10
+ text = []
11
+ lines = IO.readlines(file)
12
+ lines.each do |line|
13
+ text.push line
14
+ end
15
+ text
16
+ end
17
+
18
+ def self.read_file_by_path(file_path)
19
+ file = File.absolute_path(file_path)
20
+ data = []
21
+ lines = IO.readlines(file)
22
+ lines.each do |line|
23
+ data.push line
24
+ end
25
+ data
26
+ end
27
+
28
+ def self.write_array_to_text(array,filename)
29
+ Base.file_exists?(filename) ? file = Base.file_exists?(filename) : file = Base.create_file(filename)
30
+ File.open(file,"w") do |line|
31
+ array.each_with_index { |item,index | line.print(index,":",item);line.puts "\n" }
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,31 @@
1
+
2
+ Then /^我看见文本"([^\"]*)"$/ do |text|
3
+ wait_for_text(text, timeout: 10)
4
+ end
5
+
6
+ Then /^我看见"([^\"]*)"$/ do |text|
7
+ wait_for_text(text, timeout: 10)
8
+ end
9
+
10
+ Then /^我必须看见"([^\"]*)"$/ do |text|
11
+ wait_for_text(text, timeout: 10)
12
+ end
13
+
14
+ Then /^我必须看见文本包含"([^\"]*)"$/ do |text|
15
+ wait_for_text(text, timeout: 10)
16
+ end
17
+
18
+
19
+
20
+ Then /^我看不到文本"([^\"]*)"$/ do |text|
21
+ wait_for_text_to_disappear(text, timeout: 10)
22
+ end
23
+
24
+ Then /^我看不见文本"([^\"]*)"$/ do |text|
25
+ wait_for_text_to_disappear(text, timeout: 10)
26
+ end
27
+
28
+ Then /^我看不见"([^\"]*)"$/ do |text|
29
+ wait_for_text_to_disappear(text, timeout: 10)
30
+ end
31
+
@@ -0,0 +1,3 @@
1
+ Then /^点击第(\d+)个复选框$/ do |index|
2
+ tap_when_element_exists("android.widget.CheckBox index:#{index.to_i-1}")
3
+ end
@@ -0,0 +1,17 @@
1
+ Then /^长按"([^\"]*)"并选择第(\d+)个文本$/ do |text, index|
2
+ step_deprecated
3
+
4
+ long_press_when_element_exists("* {text CONTAINS[c] '#{text}'}")
5
+ tap_when_element_exists("com.android.internal.view.menu.ListMenuItemView android.widget.TextView index:#{index.to_i - 1}")
6
+ end
7
+
8
+ Then /^长按"([^\"]*)"并选择文本"([^\"]*)"$/ do |text, identifier|
9
+ step_deprecated
10
+
11
+ long_press_when_element_exists("* {text CONTAINS[c] '#{text}'}")
12
+ tap_when_element_exists("com.android.internal.view.menu.ListMenuItemView android.widget.TextView marked:'#{identifier}'")
13
+ end
14
+
15
+ Then /^长按"([^\"]*)"$/ do |text|
16
+ long_press_when_element_exists("* {text CONTAINS[c] '#{text}'}")
17
+ end
@@ -0,0 +1,8 @@
1
+
2
+ Given /^我在第([^\"]*)个日期选择器上选择日期"(\d\d-\d\d-\d\d\d\d)"$/ do |index,date|
3
+ set_date("android.widget.DatePicker index:#{index.to_i-1}", date)
4
+ end
5
+
6
+ Given /^我将日期选择器的"([^\"]*)"日期设为"(\d\d-\d\d-\d\d\d\d)"$/ do |content_description, date|
7
+ set_date("android.widget.DatePicker {contentDescription LIKE[c] '#{content_description}'}", date)
8
+ end
@@ -0,0 +1,23 @@
1
+ Then /^我在文本框"([^\"]*)"输入"([^\"]*)"$/ do |text, content_description|
2
+ enter_text("android.widget.EditText {contentDescription LIKE[c] '#{content_description}'}", text)
3
+ end
4
+
5
+ Then /^我输入"([^\"]*)"到第(\d+)个输入框$/ do |text, index|
6
+ enter_text("android.widget.EditText index:#{index.to_i-1}", text)
7
+ end
8
+
9
+ Then /^我输入"([^\"]*)"到输入框其id为"([^\"]*)"$/ do |text, id|
10
+ enter_text("android.widget.EditText id:'#{id}'", text)
11
+ end
12
+
13
+ Then /^我清空编辑框"([^\"]*)"$/ do |identifier|
14
+ clear_text_in("android.widget.EditText marked:'#{identifier}'}")
15
+ end
16
+
17
+ Then /^我清空第(\d+)个编辑框$/ do |index|
18
+ clear_text_in("android.widget.EditText index:#{index.to_i-1}")
19
+ end
20
+
21
+ Then /^我清空编辑框其id为"([^\"]*)"$/ do |id|
22
+ clear_text_in("android.widget.EditText id:'#{id}'")
23
+ end
@@ -0,0 +1,7 @@
1
+ Then /^我到达地理位置"([^\"]*)"$/ do |location|
2
+ set_gps_coordinates_from_location(location)
3
+ end
4
+
5
+ Then /^我在纬度([-+]?[0-9]*\.?[0-9]+),经度([-+]?[0-9]*\.?[0-9]+)$/ do |latitude, longitude|
6
+ set_gps_coordinates(latitude, longitude)
7
+ end
@@ -0,0 +1,47 @@
1
+ Then /^我返回$/ do
2
+ press_back_button
3
+ end
4
+
5
+ Then /^我点击菜单$/ do
6
+ press_menu_button
7
+ end
8
+
9
+ Then /^我点击回车键$/ do
10
+ press_user_action_button
11
+ # Or, possibly, press_enter_button
12
+ end
13
+
14
+
15
+ Then /^向左滑动$/ do
16
+ perform_action('swipe', 'left')
17
+ end
18
+
19
+ Then /^向右滑动$/ do
20
+ perform_action('swipe', 'right')
21
+ end
22
+
23
+ Then /^我从菜单中选择"([^\"]*)"$/ do |identifier|
24
+ select_options_menu_item(identifier)
25
+ end
26
+
27
+ Then /^我选择第(\d+)个tab$/ do | tab |
28
+ touch("android.widget.TabWidget descendant TextView index:#{tab.to_i-1}")
29
+ end
30
+
31
+ # @param - the "tag" associated with the tab, or the text within the tab label
32
+ Then /^我选择"([^\"]*)"tab$/ do | tab |
33
+ touch("android.widget.TabWidget descendant TextView {text LIKE[c] '#{tab}'}")
34
+ end
35
+
36
+ Then /^向下滚动$/ do
37
+ scroll_down
38
+ end
39
+
40
+ Then /^向上滚动$/ do
41
+ scroll_up
42
+ end
43
+
44
+ Then /^从位置(\d+):(\d+)拖拽到位置(\d+):(\d+)共拖拽(\d+)步$/ do |from_x, from_y, to_x, to_y, steps|
45
+ perform_action('drag', from_x, to_x, from_y, to_y, steps)
46
+ end
47
+