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,73 @@
1
+ #import "UIAutoMonkey.js"
2
+ #import "handler/buttonHandler.js"
3
+ #import "handler/wbScrollViewButtonHandler.js"
4
+ #import "tuneup/tuneup.js"
5
+
6
+ // Configure the monkey: use the default configuration but a bit tweaked
7
+ monkey = new UIAutoMonkey();
8
+ monkey.config.numberOfEvents = 50; // turn off to make clear that we want minutes
9
+ monkey.config.delayBetweenEvents = 0.05;
10
+ monkey.config.eventWeights = {
11
+ tap: 100,
12
+ drag: 10,
13
+ flick: 10,
14
+ orientation: 1,
15
+ lock: 1,
16
+ pinchClose: 1,
17
+ pinchOpen: 1,
18
+ shake: 1
19
+ };
20
+
21
+ monkey.config.touchProbability = {
22
+ multipleTaps: 0.05,
23
+ multipleTouches: 0.05,
24
+ longPress: 0.05
25
+ };
26
+
27
+ monkey.config.frame = {
28
+ origin:
29
+ {
30
+ x: parseInt(UIATarget.localTarget().frontMostApp().rect().origin.x),
31
+ y: parseInt(UIATarget.localTarget().frontMostApp().rect().origin.y)+10
32
+ },
33
+ size: {
34
+ width: parseInt(UIATarget.localTarget().frontMostApp().rect().size.width),
35
+ height: parseInt(UIATarget.localTarget().frontMostApp().rect().size.height)-20
36
+ }
37
+ };// Ignore the UIAStatusBar area, avoid to drag out the notification page.
38
+
39
+ //UI Holes handlers
40
+ var handlers = [];
41
+ handlers.push(new ButtonHandler("WBBack", 10, true));
42
+ handlers.push(new WBScrollViewButtonHandler("weatherLeftBack", 5, false, 1));
43
+ handlers.push(new ButtonHandler("取消", 3, true));
44
+ handlers.push(new ButtonHandler("CloseX", 3, true));
45
+ handlers.push(new ButtonHandler("确定", 3, false));
46
+
47
+ monkey.config.conditionHandlers = handlers;
48
+
49
+ //ANR settings
50
+ var aFingerprintFunction = function() {
51
+ var mainWindow = UIATarget.localTarget().frontMostApp().mainWindow();
52
+ //if an error occurs log it and make it the fingerprint
53
+ try {
54
+ var aString = mainWindow.elementAccessorDump("tree", true);
55
+ // var aString = mainWindow.logElementTree();
56
+ // var aString = mainWindow.logElementJSON(["name"])
57
+ if (monkey.config.anrSettings.debug) {
58
+ UIALogger.logDebug("fingerprintFunction tree=" + aString);
59
+ }
60
+ }
61
+ catch (e) {
62
+ aString = "fingerprintFunction error:" + e;
63
+ UIALogger.logWarning(aString);
64
+ }
65
+ return aString;
66
+ };
67
+ monkey.config.anrSettings.fingerprintFunction = false;//false | aFingerprintFunction
68
+ monkey.config.anrSettings.eventsBeforeANRDeclared = 18; //throw exception if the fingerprint hasn't changed within this number of events
69
+ monkey.config.anrSettings.eventsBetweenSnapshots = 8; //how often (in events) to take a snapshot using the fingerprintFunction
70
+ monkey.config.anrSettings.debug = true; //log extra info on ANR state changes
71
+
72
+ // Release the monkey!
73
+ monkey.RELEASE_THE_MONKEY();
@@ -0,0 +1,111 @@
1
+ // Copyright (c) 2015 Yahoo inc. (http://www.yahoo-inc.com)
2
+
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+
10
+ // The above copyright notice and this permission notice shall be included in
11
+ // all copies or substantial portions of the Software.
12
+
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ // THE SOFTWARE.
20
+
21
+ "use strict";
22
+ //Conforms to the ConditionHandler protocol in UIAutoMonkey
23
+ //Usage
24
+ // var handlers = [ ];
25
+ // var handlerInterval = 20; //every how many events to process. Can vary by each handler, but often useful to group them
26
+ // handlers.push(new ButtonHandler("Done", handlerInterval, false)); //every 20 events, press "Done" button if found as a top level button (no nav bar).
27
+ // ...
28
+ // config.conditionHandlers = handlers
29
+ //
30
+ function ButtonHandler(buttonName, checkEveryNumber, useNavBar, optionalIsTrueFunction) {
31
+ this.buttonName = buttonName;
32
+ this.checkEveryNumber = checkEveryNumber || 10;
33
+ if (useNavBar == undefined) {
34
+ useNavBar = true;
35
+ };
36
+ this.useNavBar = useNavBar;
37
+ this.optionalIsTrueFunction = optionalIsTrueFunction || null;
38
+ //stats
39
+ this.statsIsTrueInvokedCount = 0;
40
+ this.statsIsTrueReturnedTrue = 0;
41
+ this.statsIsTrueReturnedFalse = 0;
42
+ this.statsHandleInvokedCount = 0;
43
+ this.statsHandleNotValidAndVisibleCount = 0;
44
+ this.statsHandleErrorCount = 0;
45
+ }
46
+
47
+ // return true if we our button is visible
48
+ ButtonHandler.prototype.isTrue = function(target, eventCount, mainWindow) {
49
+ this.statsIsTrueInvokedCount++;
50
+ var result;
51
+ if (this.optionalIsTrueFunction == null) {
52
+ var aButton = this.findButton(target);
53
+ // result = aButton.isNotNil() && aButton.validAndVisible();
54
+ result = aButton.isNotNil() && aButton.isValid() && aButton.isVisible();
55
+ } else {
56
+ result = this.optionalIsTrueFunction(target, eventCount, mainWindow);
57
+ }
58
+ if (result) {
59
+ this.statsIsTrueReturnedTrue++;
60
+ } else {
61
+ this.statsIsTrueReturnedFalse++;
62
+ };
63
+ return result;
64
+ };
65
+
66
+ ButtonHandler.prototype.findButton = function(target) {
67
+ return this.useNavBar ?
68
+ target.frontMostApp().mainWindow().navigationBar().buttons()[this.buttonName] :
69
+ target.frontMostApp().mainWindow().buttons()[this.buttonName];
70
+ };
71
+
72
+ //every checkEvery() number of events our isTrue() method will be queried.
73
+ ButtonHandler.prototype.checkEvery = function() {
74
+ return this.checkEveryNumber;
75
+ };
76
+
77
+ // if true then after we handle an event consider the particular Monkey event handled, and don't process the other condition handlers.
78
+ ButtonHandler.prototype.isExclusive = function() {
79
+ return true;
80
+ };
81
+
82
+ // Press our button
83
+ ButtonHandler.prototype.handle = function(target, mainWindow) {
84
+ this.statsHandleInvokedCount++;
85
+ var button = this.findButton(target);
86
+ if (button.isValid() && button.isVisible()) {
87
+ try{
88
+ button.tap();
89
+ } catch(err) {
90
+ this.statsHandleErrorCount++;
91
+ UIALogger.logWarning(err);
92
+ }
93
+ } else {
94
+ this.statsHandleNotValidAndVisibleCount++
95
+ //UIALogger.logWarning(this.toString() + " button is not validAndVisible");
96
+ };
97
+ };
98
+
99
+ ButtonHandler.prototype.toString = function() {
100
+ return ["MonkeyTest::ButtonHandler(" + this.buttonName, this.checkEveryNumber, this.useNavBar, ")"].join();
101
+ };
102
+
103
+ ButtonHandler.prototype.logStats = function() {
104
+ UIALogger.logDebug([this.toString(),
105
+ "IsTrueInvokedCount", this.statsIsTrueInvokedCount,
106
+ "IsTrueReturnedTrue", this.statsIsTrueReturnedTrue,
107
+ "IsTrueReturnedFalse", this.statsIsTrueReturnedFalse,
108
+ "HandleInvokedCount", this.statsHandleInvokedCount,
109
+ "HandleNotValidAndVisibleCount", this.statsHandleNotValidAndVisibleCount,
110
+ "HandleErrorCount", this.statsHandleErrorCount].join());
111
+ };
@@ -0,0 +1,114 @@
1
+ // Copyright (c) 2015 Yahoo inc. (http://www.yahoo-inc.com)
2
+
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+
10
+ // The above copyright notice and this permission notice shall be included in
11
+ // all copies or substantial portions of the Software.
12
+
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ // THE SOFTWARE.
20
+
21
+ "use strict";
22
+ //Conforms to the ConditionHandler protocol in UIAutoMonkey
23
+ //Usage
24
+ // var handlers = [ ];
25
+ // var handlerInterval = 20; //every how many events to process. Can vary by each handler, but often useful to group them
26
+ // handlers.push(new ButtonHandler("Done", handlerInterval, false)); //every 20 events, press "Done" button if found as a top level button (no nav bar).
27
+ // ...
28
+ // config.conditionHandlers = handlers
29
+ //
30
+ function WBScrollViewButtonHandler(buttonName, checkEveryNumber, useNavBar, scrollViewIndex, optionalIsTrueFunction) {
31
+ this.buttonName = buttonName;
32
+ this.scrollViewIndex = scrollViewIndex;
33
+ this.checkEveryNumber = checkEveryNumber || 10;
34
+ if (useNavBar == undefined) {
35
+ useNavBar = true;
36
+ };
37
+ this.useNavBar = useNavBar;
38
+ this.optionalIsTrueFunction = optionalIsTrueFunction || null;
39
+ //stats
40
+ this.statsIsTrueInvokedCount = 0;
41
+ this.statsIsTrueReturnedTrue = 0;
42
+ this.statsIsTrueReturnedFalse = 0;
43
+ this.statsHandleInvokedCount = 0;
44
+ this.statsHandleNotValidAndVisibleCount = 0;
45
+ this.statsHandleErrorCount = 0;
46
+ }
47
+
48
+ // return true if we our button is visible
49
+ WBScrollViewButtonHandler.prototype.isTrue = function(target, eventCount, mainWindow) {
50
+ this.statsIsTrueInvokedCount++;
51
+ var result;
52
+ if (this.optionalIsTrueFunction == null) {
53
+ var aButton = this.findButton(target);
54
+ // result = aButton.isNotNil() && aButton.validAndVisible();
55
+ result = aButton.isNotNil() && aButton.isValid();
56
+ } else {
57
+ result = this.optionalIsTrueFunction(target, eventCount, mainWindow);
58
+ }
59
+ if (result) {
60
+ this.statsIsTrueReturnedTrue++;
61
+ } else {
62
+ this.statsIsTrueReturnedFalse++;
63
+ };
64
+ return result;
65
+ };
66
+
67
+ WBScrollViewButtonHandler.prototype.findButton = function(target) {
68
+ return this.useNavBar ?
69
+ target.frontMostApp().mainWindow().navigationBar().buttons()[this.buttonName]:
70
+ target.frontMostApp().mainWindow().scrollViews()[this.scrollViewIndex].buttons()[this.buttonName];
71
+ };
72
+
73
+ //every checkEvery() number of events our isTrue() method will be queried.
74
+ WBScrollViewButtonHandler.prototype.checkEvery = function() {
75
+ return this.checkEveryNumber;
76
+ };
77
+
78
+ // if true then after we handle an event consider the particular Monkey event handled, and don't process the other condition handlers.
79
+ WBScrollViewButtonHandler.prototype.isExclusive = function() {
80
+ return true;
81
+ };
82
+
83
+ // Press our button
84
+ WBScrollViewButtonHandler.prototype.handle = function(target, mainWindow) {
85
+ this.statsHandleInvokedCount++;
86
+ var button = this.findButton(target);
87
+ if (button.isValid()) {
88
+ try{
89
+ var x = button.rect().origin.x;
90
+ var y = button.rect().origin.y;
91
+ target.tap({x:x, y:y});
92
+ } catch(err) {
93
+ this.statsHandleErrorCount++;
94
+ UIALogger.logWarning(err);
95
+ }
96
+ } else {
97
+ this.statsHandleNotValidAndVisibleCount++
98
+ //UIALogger.logWarning(this.toString() + " button is not validAndVisible");
99
+ };
100
+ };
101
+
102
+ WBScrollViewButtonHandler.prototype.toString = function() {
103
+ return ["MonkeyTest::WBScrollViewButtonHandler(" + this.buttonName, this.checkEveryNumber, this.useNavBar, this.scrollViewIndex, ")"].join();
104
+ };
105
+
106
+ WBScrollViewButtonHandler.prototype.logStats = function() {
107
+ UIALogger.logDebug([this.toString(),
108
+ "IsTrueInvokedCount", this.statsIsTrueInvokedCount,
109
+ "IsTrueReturnedTrue", this.statsIsTrueReturnedTrue,
110
+ "IsTrueReturnedFalse", this.statsIsTrueReturnedFalse,
111
+ "HandleInvokedCount", this.statsHandleInvokedCount,
112
+ "HandleNotValidAndVisibleCount", this.statsHandleNotValidAndVisibleCount,
113
+ "HandleErrorCount", this.statsHandleErrorCount].join());
114
+ };
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Alex Vollmer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,402 @@
1
+ /**
2
+ * The exception thrown when a 'fail' is used.
3
+ *
4
+ * @param message - reason the test failed/aborted
5
+ */
6
+ function FailureException(message) {
7
+ this.name = 'FailureException';
8
+ this.message = message;
9
+ this.toString = function() {
10
+ return this.name + ': "' + this.message + '"';
11
+ };
12
+ }
13
+
14
+ /**
15
+ * Just flat-out fail the test with the given message
16
+ */
17
+ function fail(message) {
18
+ throw new FailureException(message);
19
+ }
20
+
21
+ /**
22
+ * Perform an assertion several times. If the assertion passes before the
23
+ * maximum number of iterations, the assertion passes. Otherwise the
24
+ * assertion fails
25
+ * @param f The function to perform (possibly) multiple times
26
+ * @param maxTries (optional) The maximum number of attempts
27
+ * @param delay (optional) The amount of time to pause between attempts
28
+ */
29
+ function retry() {
30
+ var f = arguments[0];
31
+ var maxTries = 3;
32
+ var delay = 0.5;
33
+ if (arguments.length > 1) {
34
+ maxTries = arguments[1];
35
+ }
36
+ if (arguments.length > 2) {
37
+ delay = arguments[2];
38
+ }
39
+
40
+ var tries = 0;
41
+ var exception = null;
42
+ while (tries < maxTries) {
43
+ try {
44
+ f();
45
+ return; // if we get here, our function must have passed (no exceptions)
46
+ }
47
+ catch(e) {
48
+ exception = e;
49
+ tries++;
50
+ UIATarget.localTarget().delay(delay);
51
+ }
52
+ }
53
+ throw exception;
54
+ }
55
+
56
+ /**
57
+ * The exception thrown for all assert* failures.
58
+ *
59
+ * @param message - reason the assertion failed
60
+ */
61
+ function AssertionException(message) {
62
+ this.name = 'AssertionException';
63
+ this.message = message;
64
+ this.toString = function() {
65
+ return this.name + ': "' + this.message + '"';
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Asserts that the given expression is true and throws an exception with
71
+ * a default message, or the optional +message+ parameter
72
+ */
73
+ function assertTrue(expression, message) {
74
+ if (! expression) {
75
+ if (! message) {
76
+ message = "Assertion failed";
77
+ }
78
+ throw new AssertionException(message);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Asserts that the given regular expression matches the result of the
84
+ * given message.
85
+ * @param pattern - the pattern to match
86
+ * @param expression - the expression to match against
87
+ * @param message - an optional string message
88
+ */
89
+ function assertMatch(regExp, expression, message) {
90
+ var defMessage = "'" + expression + "' does not match '" + regExp + "'";
91
+ assertTrue(regExp.test(expression), message ? message + ": " + defMessage : defMessage);
92
+ }
93
+
94
+ /**
95
+ * Assert that the +received+ object matches the +expected+ object (using
96
+ * plain ol' ==). If it doesn't, this method throws an exception with either
97
+ * a default message, or the one given as the last (optional) argument
98
+ */
99
+ function assertEquals(expected, received, message) {
100
+ var defMessage = "Expected <" + expected + "> but received <" + received + ">";
101
+ assertTrue(expected == received, message ? message + ": " + defMessage : defMessage);
102
+ }
103
+
104
+ /**
105
+ * Assert that the +received+ object does not matches the +expected+ object (using
106
+ * plain ol' !=). If it doesn't, this method throws an exception with either
107
+ * a default message, or the one given as the last (optional) argument
108
+ */
109
+ function assertNotEquals(expected, received, message) {
110
+ var defMessage = "Expected not <" + expected + "> but received <" + received + ">";
111
+ assertTrue(expected != received, message ? message + ": " + defMessage : defMessage);
112
+ }
113
+
114
+ /**
115
+ * Asserts that the given expression is false and otherwise throws an
116
+ * exception with a default message, or the optional +message+ parameter
117
+ */
118
+ function assertFalse(expression, message) {
119
+ assertTrue(! expression, message);
120
+ }
121
+
122
+ /**
123
+ * Asserts that the given object is null or UIAElementNil (UIAutomation's
124
+ * version of a null stand-in). If the given object is not one of these,
125
+ * an exception is thrown with a default message or the given optional
126
+ * +message+ parameter.
127
+ */
128
+ function assertNull(thingie, message) {
129
+ var defMessage = "Expected a null object, but received <" + thingie + ">";
130
+ // TODO: string-matching on UIAElementNil makes my tummy feel bad. Fix it.
131
+ assertTrue(thingie === null || thingie.toString() == "[object UIAElementNil]",
132
+ message ? message + ": " + defMessage : defMessage);
133
+ }
134
+
135
+ /**
136
+ * Asserts that the given object is not null or UIAElementNil (UIAutomation's
137
+ * version of a null stand-in). If it is null, an exception is thrown with
138
+ * a default message or the given optional +message+ parameter
139
+ */
140
+ function assertNotNull(thingie, message) {
141
+ var defMessage = "Expected not null object";
142
+ assertTrue(thingie !== null && thingie.toString() != "[object UIAElementNil]",
143
+ message ? message + ": " + defMessage : defMessage);
144
+ }
145
+
146
+ function OnPassException(message) {
147
+ this.name = 'OnPassException';
148
+ this.message = message;
149
+ this.toString = function() {
150
+ return this.name + ': "' + this.message + '"';
151
+ };
152
+ }
153
+
154
+ function PropertyMismatchException(propName, expected, given) {
155
+ this.name = 'PropertyMismatchException';
156
+ this.message = propName + ": expected <" + expected + "> given <" + given + ">";
157
+ this.toString = function() {
158
+ return this.name + ": " + this.message;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Assert that the given definition matches the given element. The
164
+ * definition is a JavaScript object whose property hierarchy matches
165
+ * the given UIAElement. Property names in the given definition that match a
166
+ * method will cause that method to be invoked and the matching to be performed
167
+ * and the result. For example, the UITableView exposes all UITableViewCells through
168
+ * the cells() method. You only need to specify a 'cells' property to
169
+ * cause the method to be invoked.
170
+ */
171
+ function assertElementTree(element, definition) {
172
+ var onPass = null;
173
+ if (definition.onPass) {
174
+ onPass = definition.onPass;
175
+ delete definition.onPass;
176
+ }
177
+
178
+ try {
179
+ assertPropertiesMatch(definition, element, 0);
180
+ }
181
+ catch(e) {
182
+ fail(e.toString())
183
+ }
184
+
185
+ if (onPass) {
186
+ try {
187
+ onPass(element);
188
+ }
189
+ catch(e) {
190
+ throw new OnPassException("Failed to execute 'onPass' callback: " + e);
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Assert that the given window definition matches the current main window. The
197
+ * window definition is a JavaScript object whose property hierarchy matches
198
+ * the main UIAWindow. Property names in the given definition that match a
199
+ * method will cause that method to be invoked and the matching to be performed
200
+ * and the result. For example, the UIAWindow exposes all UITableViews through
201
+ * the tableViews() method. You only need to specify a 'tableViews' property to
202
+ * cause the method to be invoked.
203
+ *
204
+ * PROPERTY HIERARCHY Property definitions can be nested as deeply as
205
+ * necessary. Matching is done by traversing the same path in the main
206
+ * UIAWindow as your screen definition. For example, to make assertions about
207
+ * the left and right buttons in a UINavigationBar you can do this:
208
+ *
209
+ * assertWindow({
210
+ * navigationBar: {
211
+ * leftButton: { name: "Back" },
212
+ * rightButton: ( name: "Done" },
213
+ * }
214
+ * });
215
+ *
216
+ * PROPERTY MATCHERS For each property you wish to make an assertion about, you
217
+ * can specify a string, number regular expression or function. Strings and
218
+ * numbers are matches using the assertEquals() method. Regular expressions are
219
+ * matches using the assertMatch() method.
220
+ *
221
+ * If you specify 'null' for a property, it means you don't care to match.
222
+ * Typically this is done inside of arrays where you need to match the number
223
+ * of elements, but don't necessarily care to make assertions about each one.
224
+ *
225
+ * Functions are given the matching property as the single argument. For
226
+ * example:
227
+ *
228
+ * assertWindow({
229
+ * navigationBar: {
230
+ * leftButton: function(button) {
231
+ * // make custom assertions here
232
+ * }
233
+ * }
234
+ * });
235
+ *
236
+ * ARRAYS
237
+ * If a property you want to match is an array (e.g. tableViews()), you can
238
+ * specify one of the above matchers for each element of the array. If the
239
+ * number of provided matchers does not match the number of given elements, the
240
+ * assertion will fail (throw an exception)
241
+ *
242
+ * In any case, you specify another object definition for each property to
243
+ * drill-down into the atomic properties you wish to test. For example:
244
+ *
245
+ * assertWindow({
246
+ * navigationBar: {
247
+ * leftButton: { name: "Back" },
248
+ * rightButton: ( name: "Done" },
249
+ * },
250
+ * tableViews: [
251
+ * {
252
+ * groups: [
253
+ * { name: "First Group" },
254
+ * { name: "Second Group" }
255
+ * ],
256
+ * cells: [
257
+ * { name: "Cell 1" },
258
+ * { name: "Cell 2" },
259
+ * { name: "Cell 3" },
260
+ * { name: "Cell 4" }
261
+ * ]
262
+ * }
263
+ * ]
264
+ * });
265
+ *
266
+ * HANDLING FAILURE If any match fails, an appropriate exception will be
267
+ * thrown. If you are using the test structure provided by tuneup, this will be
268
+ * caught and detailed correctly in Instruments.
269
+ *
270
+ * POST-PROCESSING If your screen definition provides an 'onPass' property that
271
+ * points to a function, that function will be invoked after all matching has
272
+ * been peformed on the current window and all assertions have passed. This
273
+ * means you can assert the structure of your screen and operate on it in one
274
+ * pass:
275
+ *
276
+ * assertWindow({
277
+ * navigationBar: {
278
+ * leftButton: { name: "Back" }
279
+ * },
280
+ * onPass: function(window) {
281
+ * var leftButton = window.navigationBar().leftButton();
282
+ * leftButton.tap();
283
+ * }
284
+ * });
285
+ */
286
+ function assertWindow(window) {
287
+ target = UIATarget.localTarget();
288
+ application = target.frontMostApp();
289
+ mainWindow = application.mainWindow();
290
+
291
+ assertElementTree(mainWindow, window);
292
+ }
293
+
294
+ /**
295
+ * Asserts that the +expected+ object matches the +given+ object by making
296
+ * assertions appropriate based on the type of each property in the
297
+ * +expected+ object. This method will recurse through the structure,
298
+ * applying assertions for each matching property path. See the description
299
+ * for +assertWindow+ for details on the matchers.
300
+ */
301
+ function assertPropertiesMatch(expected, given, level) {
302
+ for (var propName in expected) {
303
+ if (expected.hasOwnProperty(propName)) {
304
+ var expectedProp = expected[propName];
305
+
306
+ if (propName.match(/~iphone$/)) {
307
+ if (UIATarget.localTarget().model().match(/^iPad/) !== null ||
308
+ UIATarget.localTarget().name().match(/^iPad Simulator$/) !== null) {
309
+ continue; // we're on the wrong platform, ignore
310
+ }
311
+ else {
312
+ propName = propName.match(/^(.*)~iphone/)[1];
313
+ }
314
+ }
315
+ else if (propName.match(/~ipad$/)) {
316
+ if (UIATarget.localTarget().model().match(/^iPad/) === null &&
317
+ UIATarget.localTarget().name().match(/^iPad Simulator/) === null) {
318
+ continue; // we're on the wrong platform, ignore
319
+ }
320
+ else {
321
+ propName = propName.match(/^(.*)~ipad/)[1];
322
+ }
323
+ }
324
+
325
+ var givenProp = given[propName];
326
+
327
+ if (typeof(givenProp) == "function") {
328
+ try {
329
+ // We have to use eval (shudder) because calling functions on
330
+ // UIAutomation objects with () operator crashes
331
+ // See Radar bug 8496138
332
+ givenProp = eval("given." + propName + "()");
333
+ }
334
+ catch (e) {
335
+ UIALogger.logError("[" + propName + "]: Unable to evaluate against " + given);
336
+ continue;
337
+ }
338
+ }
339
+
340
+ if (givenProp === null) {
341
+ throw new AssertionException("Could not find given " + given + " property named: " + propName);
342
+ }
343
+ else {
344
+ var objType = Object.prototype.toString.call(givenProp);
345
+ if (objType == "[object UIAElementNil]") {
346
+ throw new AssertionException("found no elements for " + given.toString() + '.' + propName + "()");
347
+ }
348
+ else if (objType == "[object Undefined]") {
349
+ throw new AssertionException(given.toString() + '.' + propName + "() method not found.");
350
+ }
351
+ }
352
+
353
+ // null indicates we don't care to match
354
+ if (expectedProp === null) {
355
+ continue;
356
+ }
357
+
358
+ var expectedPropType = typeof(expectedProp);
359
+ if (expectedPropType == "string") {
360
+ assertEquals(expectedProp, givenProp);
361
+ }
362
+ else if (expectedPropType == "number") {
363
+ assertEquals(expectedProp, givenProp);
364
+ }
365
+ else if (expectedPropType == "boolean") {
366
+ assertEquals(expectedProp, givenProp);
367
+ }
368
+ else if (expectedPropType == "function") {
369
+ if (expectedProp.constructor == RegExp) {
370
+ assertMatch(expectedProp, givenProp);
371
+ }
372
+ else {
373
+ expectedProp(givenProp);
374
+ }
375
+ }
376
+ else if (expectedPropType == "object") {
377
+ if (expectedProp.constructor === Array) {
378
+ assertEquals(expectedProp.length, givenProp.length, "Length of " + propName + " does not match");
379
+ for (var i = 0; i < expectedProp.length; i++) {
380
+ var exp = expectedProp[i];
381
+ var giv = givenProp[i];
382
+ assertPropertiesMatch(exp, giv, level + 1);
383
+ }
384
+ }
385
+ else if (expectedProp.constructor === RegExp) {
386
+ assertMatch(expectedProp, givenProp);
387
+ }
388
+ else if (typeof(givenProp) == "object") {
389
+ assertPropertiesMatch(expectedProp, givenProp, level + 1);
390
+ }
391
+ else {
392
+ var message = "[" + propName + "]: Unknown type of object constructor: " + expectedProp.constructor;
393
+ UIALogger.logError(message);
394
+ throw new AssertionException(message);
395
+ }
396
+ }
397
+ else {
398
+ UIALogger.logError("[" + propName + "]: unknown type for expectedProp: " + typeof(expectedProp));
399
+ }
400
+ }
401
+ }
402
+ }