smart_monkey 0.1

Sign up to get free protection for your applications and to get access to all the features.
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