smart_monkey 0.1

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +17 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +77 -0
  6. data/Rakefile +57 -0
  7. data/Troubleshooting.md +61 -0
  8. data/VERSION +1 -0
  9. data/bin/smart_monkey +53 -0
  10. data/lib/bootstrap/css/bootstrap-responsive.css +1109 -0
  11. data/lib/bootstrap/css/bootstrap-responsive.min.css +9 -0
  12. data/lib/bootstrap/css/bootstrap.css +6167 -0
  13. data/lib/bootstrap/css/bootstrap.min.css +9 -0
  14. data/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
  15. data/lib/bootstrap/img/glyphicons-halflings.png +0 -0
  16. data/lib/bootstrap/js/bootstrap.js +2280 -0
  17. data/lib/bootstrap/js/bootstrap.min.js +6 -0
  18. data/lib/ios_device_log/deviceconsole +0 -0
  19. data/lib/smart_monkey.rb +2 -0
  20. data/lib/smart_monkey/command_helper.rb +71 -0
  21. data/lib/smart_monkey/monkey_runner.rb +549 -0
  22. data/lib/smart_monkey/templates/automation_result.xsl +61 -0
  23. data/lib/smart_monkey/templates/index.html.erb +77 -0
  24. data/lib/smart_monkey/templates/result.html.erb +110 -0
  25. data/lib/smart_monkey/templates/result_view.coffee +160 -0
  26. data/lib/smart_monkey/templates/result_view.js +250 -0
  27. data/lib/ui-auto-monkey/UIAutoMonkey.js +470 -0
  28. data/lib/ui-auto-monkey/custom.js +73 -0
  29. data/lib/ui-auto-monkey/handler/buttonHandler.js +111 -0
  30. data/lib/ui-auto-monkey/handler/wbScrollViewButtonHandler.js +114 -0
  31. data/lib/ui-auto-monkey/tuneup/LICENSE +20 -0
  32. data/lib/ui-auto-monkey/tuneup/assertions.js +402 -0
  33. data/lib/ui-auto-monkey/tuneup/image_asserter +26 -0
  34. data/lib/ui-auto-monkey/tuneup/image_assertion.js +65 -0
  35. data/lib/ui-auto-monkey/tuneup/image_assertion.rb +102 -0
  36. data/lib/ui-auto-monkey/tuneup/lang-ext.js +76 -0
  37. data/lib/ui-auto-monkey/tuneup/screen.js +11 -0
  38. data/lib/ui-auto-monkey/tuneup/test.js +71 -0
  39. data/lib/ui-auto-monkey/tuneup/test_runner/abbreviated_console_output.rb +38 -0
  40. data/lib/ui-auto-monkey/tuneup/test_runner/colored_console_output.rb +27 -0
  41. data/lib/ui-auto-monkey/tuneup/test_runner/console_output.rb +17 -0
  42. data/lib/ui-auto-monkey/tuneup/test_runner/preprocessor.rb +25 -0
  43. data/lib/ui-auto-monkey/tuneup/test_runner/run +343 -0
  44. data/lib/ui-auto-monkey/tuneup/test_runner/xunit_output.rb +114 -0
  45. data/lib/ui-auto-monkey/tuneup/tuneup.js +6 -0
  46. data/lib/ui-auto-monkey/tuneup/tuneup_js.podspec +52 -0
  47. data/lib/ui-auto-monkey/tuneup/uiautomation-ext.js +965 -0
  48. data/smart_monkey.gemspec +112 -0
  49. data/spec/spec_helper.rb +12 -0
  50. metadata +192 -0
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "image_assertion"))
4
+
5
+ SUCCESS_EXIT_STATUS = 0
6
+ FAILED_EXIT_STATUS = 1
7
+
8
+ test_output = ARGV[0]
9
+ ref_images_path = ARGV[1]
10
+ image_name = ARGV[2]
11
+ threshold = ARGV[3]
12
+
13
+ unless (test_output && ref_images_path && image_name)
14
+
15
+ $stderr.puts('incorrect arguments')
16
+ exit FAILED_EXIT_STATUS
17
+ end
18
+
19
+ assertion_result = ImageAssertion.assert_image(File.expand_path(test_output),
20
+ File.expand_path(ref_images_path),
21
+ image_name,
22
+ threshold)
23
+ exit FAILED_EXIT_STATUS unless assertion_result
24
+
25
+ exit SUCCESS_EXIT_STATUS
26
+
@@ -0,0 +1,65 @@
1
+ var ImageAsserter = (function() {
2
+
3
+ function ImageAsserter(tuneUpPath, outputPath, refImagesPath) {
4
+ if (!outputPath || !refImagesPath || !tuneUpPath) {
5
+ throw new AssertionException("output, refImages, tuneUp pathes can't be null");
6
+ }
7
+
8
+ this.tuneUpPath = tuneUpPath;
9
+ this.outputPath = outputPath;
10
+ this.refImagesPath = refImagesPath;
11
+
12
+ var target = UIATarget.localTarget();
13
+ if (!target) {
14
+ throw new AssertionException("unable to get localTarget");
15
+ }
16
+
17
+ this.host = target.host();
18
+ if (!this.host) {
19
+ throw new AssertionException("unable to get current UAIHost");
20
+ }
21
+ }
22
+
23
+ ImageAsserter.prototype.assertImageNamed = function(imageName, threshold) {
24
+ var command,
25
+ taskResult,
26
+ assertSuccessfull = false,
27
+ SUCCESS_EXIT_CODE = 0,
28
+ TIMEOUT = 5,
29
+ args = [this.outputPath, this.refImagesPath, imageName, threshold];
30
+
31
+ command = this.tuneUpPath + '/image_asserter';
32
+ taskResult = this.host.performTaskWithPathArgumentsTimeout(command,
33
+ args,
34
+ TIMEOUT);
35
+
36
+ assertSuccessful = (taskResult.exitCode === SUCCESS_EXIT_CODE);
37
+ if (!assertSuccessful) {
38
+ UIALogger.logError(taskResult.stderr);
39
+ }
40
+
41
+ return assertSuccessful;
42
+ };
43
+
44
+ return ImageAsserter;
45
+ }());
46
+
47
+ function createImageAsserter(tuneUpPath, outputPath, refImagesPath) {
48
+ this.imageAsserter = new ImageAsserter(tuneUpPath, outputPath, refImagesPath);
49
+ }
50
+
51
+ function assertScreenMatchesImageNamed(imageName, message, threshold) {
52
+ if (!this.imageAsserter) {
53
+ throw new AssertionException("imageAsserter isn't created.");
54
+ }
55
+
56
+ UIATarget.localTarget().captureAppScreenWithName(imageName);
57
+ UIATarget.localTarget().delay(1); // delay for screenshot to be saved
58
+
59
+ var assertionPassed = this.imageAsserter.assertImageNamed(imageName, threshold);
60
+ if (!assertionPassed) {
61
+
62
+ if (!message) message = 'Assertion of the image ' + imageName + ' failed.';
63
+ throw new AssertionException(message);
64
+ }
65
+ }
@@ -0,0 +1,102 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+
4
+ class ImageAssertion
5
+
6
+ MAX_ALLOWED_DIFF_VALUE = 1.0
7
+ DIFF_IMAGE_FOLDER_NAME = 'screens_diff'
8
+
9
+ def self.assert_image(test_output, ref_images_path, image_name, threshold)
10
+ return false unless (test_output && ref_images_path && image_name)
11
+
12
+ diff_images_path = File.join(test_output, DIFF_IMAGE_FOLDER_NAME)
13
+ Dir.mkdir(diff_images_path) unless File.directory?(diff_images_path)
14
+
15
+ image_file_name = image_name + '.png'
16
+ expected_path = File.join(ref_images_path, image_file_name)
17
+ diff_path = File.join(diff_images_path, image_file_name)
18
+
19
+ run_folder_name = find_last_folder(test_output)
20
+ received_path = File.join(run_folder_name, image_file_name)
21
+
22
+ print_status(create_status('started', "Asserting #{image_file_name}."))
23
+
24
+ if !File.exists?(received_path)
25
+ error = "No captured image file found at #{received_path}"
26
+ print_status(create_status('failed', error))
27
+ return false
28
+ elsif !File.exists?(expected_path)
29
+ error = "No reference image file found at #{expected_path}"
30
+ print_status(create_status('failed', error))
31
+ return false
32
+ else
33
+ result, exit_status = im_compare(expected_path, received_path, diff_path)
34
+ if exit_status == 0
35
+ return process_imagemagick_result(image_file_name, result, threshold)
36
+ elsif exit_status == 1
37
+ print_status(create_status('failed', "Images differ, check #{diff_images_path} for details. ImageMagick error: #{result}"))
38
+ else
39
+ print_status(create_status('failed', "ImageMagick comparison failed: #{result}"))
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Iterte through folders with name Run* and return with latests run number
47
+ def self.find_last_folder(test_output)
48
+ folder_mask = "#{test_output}/Run";
49
+ run_folders = Dir.glob("#{folder_mask}*")
50
+
51
+ return nil unless run_folders.length > 0
52
+
53
+ run_folders.sort do |x, y|
54
+ y.gsub(folder_mask, '').to_i <=> x.gsub(folder_mask, '').to_i
55
+ end[0]
56
+ end
57
+
58
+ def self.process_imagemagick_result(image_file_name, result, threshold)
59
+ result_status = 'failed'
60
+ result_message = "#{image_file_name} is not equal to the reference."
61
+ assertionResult = false
62
+
63
+ #imagemagick outputs floating point metrics value when succeeds
64
+ compare_succeed = ( result.match(/[0-9]*\.?[0-9]+/).length > 0 )
65
+ threshold ||= MAX_ALLOWED_DIFF_VALUE
66
+
67
+ if compare_succeed
68
+ if result.to_f < threshold
69
+
70
+ result_status = 'passed'
71
+ result_message = "#{image_file_name} asserted successfully."
72
+ assertionResult = true
73
+ else
74
+ print_status(create_status(result_status, "expected diff is smaller than #{threshold} but #{result.to_f}."))
75
+ end
76
+ else
77
+
78
+ result_message = result
79
+ end
80
+
81
+ print_status(create_status(result_status, result_message))
82
+ assertionResult
83
+ end
84
+
85
+ def self.create_status(status, message)
86
+ "#{Time.new} #{status}: #{message}"
87
+ end
88
+
89
+ def self.print_status(message)
90
+ $stderr.puts(message)
91
+ end
92
+
93
+ def self.im_compare(expected_path, received_path, diff_path)
94
+ command = '/usr/local/bin/compare -metric MAE '
95
+ command << Shellwords.escape(expected_path) + ' '
96
+ command << Shellwords.escape(received_path) + ' '
97
+ command << ( diff_path ? Shellwords.escape(diff_path) : 'null:' )
98
+
99
+ _, _, stderr, wait_thr = Open3.popen3(command)
100
+ [stderr.read, wait_thr.value.exitstatus]
101
+ end
102
+ end
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Extend +destination+ with +source+
3
+ */
4
+ function extend(destination, source) {
5
+ for (var property in source) {
6
+ destination[property] = source[property];
7
+ }
8
+ return destination;
9
+ }
10
+
11
+ /**
12
+ * Dump the properties out a String returned by the function.
13
+ */
14
+ function dumpProperties(_this) {
15
+ var dumpStr = "";
16
+ for (var propName in _this) {
17
+ if (_this.hasOwnProperty(propName)) {
18
+ if (dumpStr !== "") {
19
+ dumpStr += ", ";
20
+ }
21
+ dumpStr += (propName + "=" + _this[propName]);
22
+ }
23
+ }
24
+
25
+ return dumpStr;
26
+ };
27
+
28
+ function getMethods(_this) {
29
+ var methods = [];
30
+ for (var m in _this) {
31
+ //if (typeof _this[m] == "function" && _this.hasOwnProperty(m)) {
32
+ if (typeof _this[m] == "function") {
33
+ methods.push(m);
34
+ }
35
+ }
36
+ return methods;
37
+ }
38
+
39
+ extend(Array.prototype, {
40
+ /**
41
+ * Applies the given function to each element in the array until a
42
+ * match is made. Otherwise returns null.
43
+ * */
44
+ contains: function(f) {
45
+ for (i = 0; i < this.length; i++) {
46
+ if (f(this[i])) {
47
+ return this[i];
48
+ }
49
+ }
50
+ return null;
51
+ },
52
+
53
+ unique: function() {
54
+ function onlyUnique(value, index, self) {
55
+ return self.indexOf(value) === index;
56
+ }
57
+
58
+ return this.filter(onlyUnique);
59
+ }
60
+ });
61
+
62
+ String.prototype.trim = function() {
63
+ return this.replace(/^\s+|\s+$/g,"");
64
+ };
65
+
66
+ String.prototype.ltrim = function() {
67
+ return this.replace(/^\s+/,"");
68
+ };
69
+
70
+ String.prototype.rtrim = function() {
71
+ return this.replace(/\s+$/,"");
72
+ };
73
+
74
+ String.prototype.lcfirst = function() {
75
+ return this.charAt(0).toLowerCase() + this.substr(1);
76
+ };
@@ -0,0 +1,11 @@
1
+ Screen = {
2
+ screens: {},
3
+
4
+ add: function(name, definition) {
5
+ this.screens[name] = definition;
6
+ },
7
+
8
+ named: function(name) {
9
+ return this.screens[name];
10
+ }
11
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Run a new test with the given +title+ and function body, which will
3
+ * be executed within the proper test declarations for the UIAutomation
4
+ * framework. The given function will be handed a +UIATarget+ and
5
+ * a +UIApplication+ object which it can use to exercise and validate your
6
+ * application.
7
+ *
8
+ * The +options+ parameter is an optional object/hash thingie that
9
+ * supports the following:
10
+ * logTree -- a boolean to log the element tree when the test fails (default 'true')
11
+ *
12
+ * Example:
13
+ * test("Sign-In", function(target, application) {
14
+ * // exercise and validate your application.
15
+ * });
16
+ *
17
+ * The +title+ is checked against every element of a global TUNEUP_ONLY_RUN
18
+ * array. To check, each element is converted to a RegExp. The test is only
19
+ * executed, if one check succeeds. If TUNEUP_ONLY_RUN is not defined,
20
+ * no checks are performed.
21
+ */
22
+ function test(title, f, options) {
23
+ if (typeof TUNEUP_ONLY_RUN !== 'undefined') {
24
+ for (var i = 0; i < TUNEUP_ONLY_RUN.length; i++) {
25
+ if (new RegExp("^" + TUNEUP_ONLY_RUN[i] + "$").test(title)) {
26
+ break;
27
+ }
28
+ if (i == TUNEUP_ONLY_RUN.length -1) {
29
+ return;
30
+ }
31
+ }
32
+ }
33
+
34
+ if (!options) {
35
+ options = testCreateDefaultOptions();
36
+ }
37
+ target = UIATarget.localTarget();
38
+ application = target.frontMostApp();
39
+ UIALogger.logStart(title);
40
+ try {
41
+ target.logDeviceInfo();//打印设备信息
42
+ f(target, application);
43
+ UIALogger.logPass(title);
44
+ }
45
+ catch (e) {
46
+ UIALogger.logError(e.toString());
47
+ if (options.logStackTrace) UIALogger.logError(e.stack);
48
+ if (options.logTree) target.logElementTree();
49
+ if (options.logTreeJSON) application.mainWindow().logElementTreeJSON();
50
+ if (options.screenCapture) target.captureScreenWithName(title + '-fail');
51
+ UIALogger.logFail(title);
52
+ }
53
+ finally{
54
+ target.captureScreenWithName('finish');
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Helper function to isolate clients from additional option changes. Clients can use this function to get a new option object and then only change the options they care about, confident that any new options added since their
60
+ * code was created will contain the new default values.
61
+ * @returns {Object} containing the error options
62
+ */
63
+ function testCreateDefaultOptions() {
64
+ return {
65
+ logStackTrace: false,
66
+ logTree: false,
67
+ logTreeJSON: false,
68
+ screenCapture: false
69
+ };
70
+ }
71
+
@@ -0,0 +1,38 @@
1
+ class AbbreviatedConsoleOutput < ColoredConsoleOutput
2
+
3
+ def add_status(status, date, time, time_zone, msg)
4
+ message = format(status, msg)
5
+ if !message.nil?
6
+ puts message
7
+ end
8
+ end
9
+
10
+ def format(status, msg)
11
+ output = nil
12
+ if status
13
+ output = self.message_for_status(status, msg);
14
+ output = colorize(output, STATUS_COLORS[status]) if STATUS_COLORS[status]
15
+ end
16
+ output
17
+ end
18
+
19
+ def message_for_status(status, msg)
20
+ message = nil
21
+ case status
22
+ when /^default/
23
+ message = " > #{msg}"
24
+ when /^start/
25
+ message = "\n> #{status.to_s.capitalize}: #{msg}"
26
+ when /^fail/
27
+ message = "X #{status.to_s.capitalize}: #{msg}"
28
+ when /^pass/
29
+ message = "#{status.to_s.capitalize}: #{msg}"
30
+ when /^warning/
31
+ message = " ! #{status.to_s.capitalize}: #{msg}"
32
+ when /^issue/
33
+ message = " ! #{status.to_s.capitalize}: #{msg}"
34
+ end
35
+
36
+ message
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ class ColoredConsoleOutput < ConsoleOutput
2
+ COLORS = {
3
+ :red => 31,
4
+ :green => 32,
5
+ :yellow => 33,
6
+ :cyan => 36
7
+ }
8
+
9
+ STATUS_COLORS = {
10
+ :start => :cyan,
11
+ :pass => :green,
12
+ :fail => :red,
13
+ :error => :red,
14
+ :warning => :yellow,
15
+ :issue => :yellow
16
+ }
17
+
18
+ def format(status, date, time, time_zone, msg)
19
+ output = super
20
+ output = colorize(output, STATUS_COLORS[status]) if STATUS_COLORS[status]
21
+ output
22
+ end
23
+
24
+ def colorize(text, color)
25
+ "\e[#{COLORS[color]}m#{text}\e[0m"
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ class ConsoleOutput
2
+ def add(line)
3
+ puts line
4
+ end
5
+
6
+ def add_status(status, date, time, time_zone, msg)
7
+ puts "\n" if status == :start # add a blank line before each test to visually group the output
8
+ puts format(status, date, time, time_zone, msg)
9
+ end
10
+
11
+ def format(status, date, time, time_zone, msg)
12
+ "#{date} #{time} #{time_zone} #{status.to_s.capitalize}: #{msg}"
13
+ end
14
+
15
+ def close
16
+ end
17
+ end