AXElements 0.6.0beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.yardopts +20 -0
  2. data/LICENSE.txt +25 -0
  3. data/README.markdown +150 -0
  4. data/Rakefile +109 -0
  5. data/docs/AccessibilityTips.markdown +119 -0
  6. data/docs/Acting.markdown +340 -0
  7. data/docs/Debugging.markdown +326 -0
  8. data/docs/Inspecting.markdown +255 -0
  9. data/docs/KeyboardEvents.markdown +57 -0
  10. data/docs/NewBehaviour.markdown +151 -0
  11. data/docs/Notifications.markdown +271 -0
  12. data/docs/Searching.markdown +250 -0
  13. data/docs/TestingExtensions.markdown +52 -0
  14. data/docs/images/AX.png +0 -0
  15. data/docs/images/all_the_buttons.jpg +0 -0
  16. data/docs/images/ui_hierarchy.dot +34 -0
  17. data/docs/images/ui_hierarchy.png +0 -0
  18. data/ext/key_coder/extconf.rb +6 -0
  19. data/ext/key_coder/key_coder.m +77 -0
  20. data/lib/ax_elements/accessibility/enumerators.rb +104 -0
  21. data/lib/ax_elements/accessibility/language.rb +347 -0
  22. data/lib/ax_elements/accessibility/qualifier.rb +73 -0
  23. data/lib/ax_elements/accessibility.rb +164 -0
  24. data/lib/ax_elements/core.rb +541 -0
  25. data/lib/ax_elements/element.rb +593 -0
  26. data/lib/ax_elements/elements/application.rb +88 -0
  27. data/lib/ax_elements/elements/button.rb +18 -0
  28. data/lib/ax_elements/elements/radio_button.rb +18 -0
  29. data/lib/ax_elements/elements/row.rb +30 -0
  30. data/lib/ax_elements/elements/static_text.rb +17 -0
  31. data/lib/ax_elements/elements/systemwide.rb +46 -0
  32. data/lib/ax_elements/inspector.rb +116 -0
  33. data/lib/ax_elements/macruby_extensions.rb +255 -0
  34. data/lib/ax_elements/notification.rb +37 -0
  35. data/lib/ax_elements/version.rb +9 -0
  36. data/lib/ax_elements.rb +30 -0
  37. data/lib/minitest/ax_elements.rb +19 -0
  38. data/lib/mouse.rb +185 -0
  39. data/lib/rspec/expectations/ax_elements.rb +15 -0
  40. data/test/elements/test_application.rb +72 -0
  41. data/test/elements/test_row.rb +27 -0
  42. data/test/elements/test_systemwide.rb +38 -0
  43. data/test/helper.rb +119 -0
  44. data/test/test_accessibility.rb +127 -0
  45. data/test/test_blankness.rb +26 -0
  46. data/test/test_core.rb +448 -0
  47. data/test/test_element.rb +939 -0
  48. data/test/test_enumerators.rb +81 -0
  49. data/test/test_inspector.rb +121 -0
  50. data/test/test_language.rb +157 -0
  51. data/test/test_macruby_extensions.rb +303 -0
  52. data/test/test_mouse.rb +5 -0
  53. data/test/test_search_semantics.rb +143 -0
  54. metadata +219 -0
@@ -0,0 +1,271 @@
1
+ # Notifications
2
+
3
+ @todo This document needs to be audited for accuracy. The interface
4
+ for notifications is in flux and this document might be out of sync
5
+ with the current implementation.
6
+
7
+ Accessibility notifications are a tool that you can use to add a delay
8
+ in script that waits until a certain event happens. These events could
9
+ be a window being created, a menu being opened, or one of a plethora
10
+ of other events.
11
+
12
+ A notification is much more time efficient than simply using `sleep`
13
+ to estimate how long an action will take to complete. Sleep often
14
+ causes you to wait longer than is necessary to avoid waiting less time
15
+ than what is needed. With notifications you set a maximum amount of
16
+ time that should be waited, called the timeout, but waiting is stopped
17
+ as soon as the notification is received. This makes using
18
+ notifications preferable to simply sleeping.
19
+
20
+ An important thing to understand is that an accessibility notification
21
+ is separate from the notifications that are sent and received
22
+ internally in a Cocoa app. Accessibility notifications are not very
23
+ different in concept, they differ largely in mechanics. This is
24
+ because talking to another application,
25
+ [across processes](http://en.wikipedia.org/wiki/Inter-process_communication),
26
+ is expensive and would be impractical to do for every notification
27
+ that is sent internally in an application. This makes a bit more
28
+ difficult to track down what notification is going to be sent through
29
+ accessibility; using notifications more difficult than simply using
30
+ `sleep`, at least until you have a lot of practice.
31
+
32
+ ## Simple Example
33
+
34
+ Consider a log in window. You enter some credentials and then you
35
+ press a button to log in and load up the main application window.
36
+ This procedure is most likely asynchronous, it usually takes one or
37
+ two seconds, but could take longer. To script logging in you would
38
+ have to wait until the main application window loaded. You could just
39
+ use `Kernel#sleep`, but how long should you sleep for and how reliable
40
+ is that? How much time would that waste?
41
+
42
+ Notifications are perfect for this scenario; whenever a window is
43
+ created, the application sends out a notification to any accessibility
44
+ applications that are listening letting them know a window was
45
+ created; the notification will even include the object that sent the
46
+ notification and the name of the notification. Using this system, we
47
+ can just wait for the notification to be received and continue
48
+ executing the script right away (technically, there can be few
49
+ milliseconds of delay). A typical example of using notifications looks
50
+ like this:
51
+
52
+ register_for_notification app, :window_created
53
+ login # trigger an action that will eventually send a notification
54
+ wait_for_notification
55
+
56
+ And that's it.
57
+
58
+ ## One-Two-Three Combo
59
+
60
+ Using notifications is a three step process: first a left jab to set up
61
+ the notification, then you dodge the counter punch, and then you
62
+ finish with a right cross to wait for the notification. If you're left
63
+ handed then you can jab with the right and cross with the left (but
64
+ you still setup first and then wait).
65
+
66
+ ### First You Set It Up
67
+
68
+ Setup is the important step and is fairly painless as far as what
69
+ you need to provide for AXElements. To register for a notification you
70
+ call {Accessibility::Language#register_for_notification}. You pass the
71
+ the {AX::Element} who will be sending the notification and then the
72
+ notification name. Another simple example would be:
73
+
74
+ register_for_notification text_field, :value_changed
75
+
76
+ In this hypothetical case, we want to wait for a `:value_changed`
77
+ notification that will be sent by `text_field`. That's it. And that
78
+ also covers 90% of the cases where you will use notifications.
79
+
80
+ #### Applications Are Special
81
+
82
+ Listening from the application, an {AX::Application}, object is a
83
+ special case. When you say that an {AX::Application} object will be
84
+ the sender, what you are saying is that any UI element in the app will
85
+ be the sender and you don't really care as long as you get the right
86
+ notification name (that's not entirely true, but a good enough lie for
87
+ now).
88
+
89
+ #### Notification Names or: How Is Babby Formed?
90
+
91
+ Like attribute and action name translation, notification names are
92
+ also translated for your convenience. However, where do you get the
93
+ notification names that Apple provides?
94
+
95
+ It turns out that there is no API for getting a list of notifications
96
+ that an element can send---Apple dropped the ball in this case, but
97
+ they did provide a
98
+ [list of notifications](http://developer.apple.com/library/mac/#documentation/UserExperience/Reference/Accessibility_RoleAttribute_Ref/Notifications.html#//apple_ref/doc/uid/TP40007870-Notifications-SW1)
99
+ which has been nicely updated for Mac OS X Lion. The list of
100
+ notifications is __required__ reading if you want to use accessibility
101
+ notifications.
102
+
103
+ For custom notifications, which I'll get to in a bit, you shouldn't
104
+ (read: don't) do a name translation unless you provide an equivalent
105
+ constant for the MacRuby run time. Instead, you should just provide
106
+ the string with the notification name:
107
+
108
+ register_for_notification app, 'customNotification'
109
+ register_for_notification app, 'Cheezburger''
110
+
111
+ ### Do Something
112
+
113
+ To trigger the action, you perform an action. This was already covered
114
+ in the {file:docs/Acting.markdown Acting tutorial}, which is probably
115
+ why you are reading this tutorial.
116
+
117
+ ### Waiting...
118
+
119
+ Once the action has been triggered, you simply need to wait for the
120
+ notification to be received:
121
+
122
+ wait_for_notification
123
+
124
+ And then you wait (if you are the computer or just enjoy watching your
125
+ scripts execute). If all goes well you will receive the notification,
126
+ and the script continues on to the next statement. By default,
127
+ AXElements will wait for 10 seconds, and if the notification is not
128
+ received then the script will continue anyways.
129
+
130
+ Waiting for longer, or less, time than the default can be done by
131
+ passing a parameter to `wait_for_notification`. For instance, you
132
+ could wait for a minute or a second:
133
+
134
+ wait_for_notification 60.0
135
+ wait_for_notification 1.0
136
+
137
+ ## A Bit More Complicated
138
+
139
+ The above should cover most cases, but notifications can be more
140
+ complex than those examples. You might need to unregister for
141
+ notifications, or add more complex logic to a notification
142
+ registration.
143
+
144
+ ### Unregister Notifications
145
+
146
+ If you need to unregister for a notifications then you can. The DSL
147
+ layer provides {Accessibility::Language#unregister_notifications}, and
148
+ hopefully more options in the future.
149
+
150
+ Using `unregister_notifications`, you can unregister _all_
151
+ notifications. This is actually done in cases where a notification is
152
+ not received, simply to clear out possible problems with lingering
153
+ registrations that might mess up future waiting.
154
+
155
+ ### You Are The Decider
156
+
157
+ When you setup a notification you can optionally pass a block that
158
+ decides whether or not the received notification is the one you
159
+ wanted. You will be given the element that sent the notification,
160
+ which is not necessarily the element that you registered with, and the
161
+ name of the notification. In this case you would register for a
162
+ notification like so:
163
+
164
+ register_for_notification app, :window_created do |window, notification|
165
+ window.title == 'New Contact'
166
+ end
167
+
168
+ This gives you some extra security. Instead of continuing the script
169
+ execution after getting the first `:window_created` notification, the
170
+ script will remain paused until the element that sends the
171
+ notification has a title of `'New Contact'`.
172
+
173
+ The contract is quite simple. If you do not pass a block, then the
174
+ first notification received will cause AXElements to stop waiting; if
175
+ you want to pass a block then the block must return tuthy or falsy
176
+ to decide whether the received notification is the one that you
177
+ wanted. You will be given the sender of the notification as well as
178
+ the notification name; though the notification name will usually not
179
+ be very interesting since it is the value you registered with.
180
+
181
+ ### More Than One Notification
182
+
183
+ You can register for as many notifications as you want, but I don't
184
+ recommend that you register for more than one at a time unless you
185
+ know what you are doing. The reason why is that when you call
186
+ `wait_for_notification`, you are pausing script execution until any of
187
+ the notifications you have registered for are received. The block that
188
+ you pass will only be used for notifications that are sent to the
189
+ registration you set it up with.
190
+
191
+ This is why AXElements unregisters everything if it fails to receive a
192
+ notification. This detail might also screw you over if you are trying
193
+ to work with more than one notification at once and should be fixed in
194
+ the future.
195
+
196
+ ### Rainbows And Unicorns
197
+
198
+ As mentioned earlier, accessibility notifications are not all rainbows
199
+ and unicorns. If you've played with notifications while going through
200
+ this tutorial then you may have noticed a problem or two with the
201
+ design.
202
+
203
+ #### Who Sends The Notification
204
+
205
+ Documentation is the only hint you really get about who will send
206
+ particular notifications. If you are confident that a certain
207
+ notification will be sent but do not know who will be sending the
208
+ notification, then you can register with the {AX::Application} object
209
+ to be the sender of the notification. This causes you to receive
210
+ notifications from any object as long as it has the proper
211
+ notification type. If you use this feature in conjunction with passing
212
+ a block that always returns false, then you can capture all the
213
+ notifications for a period of time and find out who is sending the
214
+ notification. An example would look something like this:
215
+
216
+ register_for_notification app, :value_changed do |element, notif|
217
+ puts element.inspect
218
+ false
219
+ end
220
+
221
+ # trigger some action
222
+
223
+ wait_for_notification 30.0
224
+
225
+ Since you are returning `false` at the end of the block, the script
226
+ will pause until a timeout occurs and you will see the `#inspect`
227
+ output for each object that sends the notification.
228
+
229
+ ### What Is Known To Work
230
+
231
+ - `:value_changed` is only sent by objects that have a `value`
232
+ attribute, and should be sent every time that the value is changed,
233
+ whether through accessibility or normal computer usage
234
+ - `:menu_opened` seems to only be sent by menu bar menus, not by menus
235
+ opened by pop up buttons
236
+
237
+ ## Custom Notifications
238
+
239
+ Custom notifications are pretty simple. Apple recommends that you try
240
+ to use the built in notifications as much as possible, but
241
+ acknowledges that you may need to add your own notifications
242
+ sometimes.
243
+
244
+ As noted earlier, accessibility notifications use a different system
245
+ than cocoa notifications; accessibility notifications are much simpler
246
+ to send; it is simply a C function call with two parameters:
247
+
248
+ NSAccessibilityPostNotification(self, @"Cheezburger");
249
+
250
+ The first parameter is the sender of the notification; just as with
251
+ other accessibility APIs from the server (read: app) side of things,
252
+ you pass the actual object and accessibility magic will create the
253
+ token to send to the client (read: AXElements). The one caveat in this
254
+ case is that the sender of the notification cannot be an object that is
255
+ ignored by accessibility or else the notification will not be sent. If
256
+ you are unsure whether or not the object is visible to accessibility,
257
+ then you need to call an extra function to find someone who _will_ be
258
+ visible to accessibility; these functions are listed in the
259
+ [AppKit Functions](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Miscellaneous/AppKit_Functions/Reference/reference.html#//apple_ref/doc/uid/TP40004154)
260
+ in the Accessibility section. An example would look like this:
261
+
262
+ NSAccessibilityPostNotification(NSAccessibilityUnignoredAncestor(self), @"Cheezburger");
263
+
264
+ In this case you would be looking up the hierarchy for an ancestor,
265
+ but you could also look down the hierarchy for a descendant or to the
266
+ side for a sibling.
267
+
268
+ As a rule of thumb, if you end up spending more 20minutes trying to
269
+ figure out which notification will should be listening for, and who
270
+ will be sending it, then you should just create a custom
271
+ notification. But you don't have to take my word for it.
@@ -0,0 +1,250 @@
1
+ # Searching
2
+
3
+ Searching the view hierarchy is the most powerful idea that this
4
+ toolkit provides by _significantly_ simplifying the process of
5
+ describing UI elements. Search works by looking at the child elements
6
+ of the current element, and possibly at the grandchildren, and so on
7
+ and so forth in a breadth first searching order. There are a few well
8
+ defined features of searching that make it powerful: pluralization,
9
+ attribute filtering, nesting, and element attribute inference.
10
+
11
+ ## Search Basics
12
+
13
+ First, the basic form of a search looks like this:
14
+
15
+ $ELEMENT.$KLASS($FILTER_ATTRIBUTE1: $FILTER_VALUE1, ...)
16
+
17
+ Actually, that is just the form for an implicit search, which is a
18
+ little nicer to write than an explicit search. The only difference
19
+ between the two is that an implicit search must always find something,
20
+ since we are treating it as a description, or else a
21
+ {AX::Element::SearchFailure} error will be raised.
22
+
23
+ Looking at the pieces of the statement, the obvious piece is
24
+ `$ELEMENT`; `$ELEMENT` is the element to start searching from. Then
25
+ `$KLASS`, the method name, is the class of the object to search for,
26
+ and the parameter list is just a hash where the key,
27
+ `$FILTER_ATTRIBUTE`, is an attribute on the instance of the class and
28
+ the value, `$FILTER_VALUE`, needs to match what is returned when the
29
+ attribute is looked up.
30
+
31
+ An implicit search is written as if the class of the object you were
32
+ searching for was the method and the filters would be the parameters
33
+ for the method. If we substitute real values in, an example would like
34
+ this:
35
+
36
+ window.button(title: 'Main Window')
37
+
38
+ Which means that we want to find a `button` that is a child (or
39
+ descendant) of `window`, but only if the `button` has a `title` of
40
+ `'Main Window'` on it. You can add as many filters as you want, but
41
+ generally you will only need one.
42
+
43
+ ### Pluralization
44
+
45
+ In cases where you want to find more than one object of a certain
46
+ type, you simply need to pluralize the method name. For example:
47
+
48
+ window.buttons
49
+
50
+ is translated into something like this:
51
+
52
+ ![All The Buttons](images/all_the_buttons.jpg)
53
+
54
+ It's just that easy. The rules for pluralization are the same as
55
+ English (or the local language?) since we are using the
56
+ `ActiveSupport::Inflector` to do the work of translating from
57
+ pluralized form back to the singular form. Even something like 'boxes'
58
+ will get translated back to 'box' and work as you expect.
59
+
60
+ Except for the fact that you will get a collection of UI elements back
61
+ instead of a single item, pluralized search works the same as a
62
+ non-pluralized search. You can attach any filters you could use with a
63
+ non-pluralized search, and if the search is implicit then it must find
64
+ something.
65
+
66
+ Pluralized searches are useful when you want to do some custom
67
+ refinement on a search, or if you need to make sure something is not
68
+ in the UI element tree (and hopefully that means it is not on the
69
+ screen anymore either). It can also be helpful when you want to
70
+ explore the UI element tree and find out what types of UI elements are
71
+ present on the screen. The detail to remember is that a pluralized
72
+ search will have to explore the entire UI sub-tree from the starting
73
+ point and so it could be slow.
74
+
75
+ ### Kind Of
76
+
77
+ Remember earlier when I said the method name should be the class of
78
+ the element being searched for? Well, that was kind of a lie. Kind of.
79
+ Get it? Have I killed the joke yet? Kind of? :D
80
+
81
+ The search class that you enter is actually matched using `#kind_of?`
82
+ instead of matching the classes exactly. And since class hierarchies
83
+ are properly setup, you can search for a base class and end up finding
84
+ subclasses.
85
+
86
+ For instance, not all buttons are of the class `AX::Button`, the
87
+ traffic light buttons are all different subclasses of `AX::Button`:
88
+ `AX::CloseButton`, `AX::MinimizeButton`, and `AX::ZoomButton`. There
89
+ are other several other subclasses of `AX::Button` as
90
+ well. `AX::Button` itself is a subclass of `AX::Element`. Actually,
91
+ all UI elements are a subclass of `AX::Element`. What this means is
92
+ that when you have code like:
93
+
94
+ app.close_button
95
+
96
+ You will only ever find something that is a `AX::CloseButton`, but
97
+ when you write something like:
98
+
99
+ app.button
100
+
101
+ Any button or subclass of button, including all the traffic light
102
+ buttons, can be found. I believe this makes search follow the
103
+ [DWIM](http://en.wikipedia.org/wiki/DWIM) principle, and allows you to
104
+ shorten the code you need to write in a number of cases. For example:
105
+
106
+ app.window
107
+
108
+ Can be substituted in place of
109
+
110
+ app.standard_window
111
+
112
+ to find the first window for `app`. This makes sense if there is only
113
+ one window for the app, which is often the case. Similarly, if you are
114
+ searching from a container, such as an `AX::Group`, which only has a
115
+ one button, which happens to be a `AX::SortButton`, then you can say:
116
+
117
+ table.button
118
+
119
+ Since it will not be ambiguous and AXElements knows what you
120
+ mean. What if we take it a step further, what if made an even broader
121
+ search. Since _all_ UI elements are a subclass of {AX::Element}, we
122
+ could just write something like:
123
+
124
+ app.element
125
+
126
+ Which would find the first child of `app`. If we combined this with
127
+ pluralization, we could do something like:
128
+
129
+ app.elements
130
+
131
+ Which will return an array with _all_ the descendants in it; so as always
132
+ you will need to have some awareness of the layout of the element tree
133
+ when you write a search. Otherwise you could end up finding something
134
+ completely different from what you wanted; consider what could happen
135
+ if you search for an `AX::Button` objects when you want an
136
+ `AX::CloseButton`. In these cases you will want to be more specific
137
+ about what you are looking for, which can often allow you to skip the
138
+ need for a search filter. Be specific when it sounds better and
139
+ generalize more when you can, it should make code read more
140
+ naturally.
141
+
142
+ ### Nested Searching
143
+
144
+ Sometimes you need to describe an element on the screen, and the only
145
+ reasonable way to do so is by describing some of the descendants of the
146
+ element that you are looking for. For this requirement, nested
147
+ searching exists, and very naturally too. Pretend that you didn't
148
+ already look at the next code snippet and try to guess what a nested
149
+ search looks like; you will probably be correct. __Hint__: Searches
150
+ can be nested arbitrarily deep and mixed in with other filter
151
+ parameters. The answer, by example:
152
+
153
+ window.outline.row(text_field: { value: 'Calendar' })
154
+
155
+ This would be asking for the outline row that has a text field with
156
+ the value of `'Calendar'`. The proper form for this would be:
157
+
158
+ $ELEMENT.$KLASS($DESCENDANT: { $DESCENDANT_FILTER_ATTRIBUTE: $DESCENDANT_FILTER_VALUE1, ... }, ...)
159
+
160
+ Where `$DESCENDANT` plays the same role as `$KLASS`, but for a new
161
+ search that will be applied to descendants of `$KLASS`. Nested
162
+ searching is a feature you won't need too often if the UI hierarchy
163
+ makes good use of identifiers or other attributes that can be used to
164
+ uniquely identify an element. Nested searching is best used when you
165
+ can only identify an element by describing its children. But you don't
166
+ have to take my word for it.
167
+
168
+ ### Element Attribute Inference
169
+
170
+ Element attribute inference came about because of a coding error, when
171
+ someone was trying to write some code to search for element that
172
+ matched to a title UI element. Before going over the solution I think
173
+ it would be best to explain the problem.
174
+
175
+ If an element has a title UI element attribute, then the title UI
176
+ element will end up being another UI element. The problem with this is
177
+ that you then need to know about that element before you search and
178
+ then you need to use the element as the filter value, for example:
179
+
180
+ title_field = window.text_field(value: 'Name')
181
+ window.button(title_ui_element: title_field)
182
+
183
+ While that code is legitimate, it is not the most succinct way of
184
+ writing what was meant, and maybe not as clear as it could be. Perhaps
185
+ something more like:
186
+
187
+ window.button(title_ui_element: 'Name')
188
+
189
+ In this case you are matching the `title` of the
190
+ `title_ui_element`. This works without introducing inconsistencies in
191
+ the language we have created for searching. In the example, you would
192
+ be saying that the button you are looking for will be associated with
193
+ a title UI element that says `'Name'`. You can still match against the
194
+ actual UI element if you want, but I think this is much simpler.
195
+
196
+ This is not actually implemented internally to the searching
197
+ logic, it is implemented in each class that wants to
198
+ participate. Since search works by getting the value of the search
199
+ filter and checking if it is `==` to the filter value, we just need
200
+ to implement `==` on specific classes where we want to support custom
201
+ behaviour. Examples of how this would work would be
202
+ {AX::StaticText#==} and {AX::Button#==}. The behaviour could be easily
203
+ added to other classes where it made sense. However, this type of idea
204
+ can be further expanded to cases that might not be so easily
205
+ implemented and is discussed in future prospects.
206
+
207
+ ## Explicit Search
208
+
209
+ In the off chance that you need to make an explicit search, you can
210
+ trigger a search through {AX::Element#search}. In this case you give
211
+ the `$KLASS` as the first parameter of the method and the filters are
212
+ the remaining parameters. As with {AX::Element#attribute}, this is
213
+ meant for performance in cases of heavy searching; you should avoid
214
+ using it unless you know what you are doing.
215
+
216
+ ## Caveats
217
+
218
+ The only caveat to note right now is that a UI element will always
219
+ return `false` when you ask if it can `#respond_to?` something that
220
+ would be an implicit search. This is because of the semantics of a
221
+ search do not make sense in the context of `#respond_to?` and would be
222
+ very expensive. You would need to perform the search in order to know
223
+ if the search would succeed.
224
+
225
+ ## The Future
226
+
227
+ Right now, the only case that is not handled is filtering by
228
+ parameterized attribute. The problem with this case is that I am not
229
+ sure how to work it into the existing syntax or how to change the
230
+ existing syntax. Since you also need to encode the parameter with the
231
+ attribute it is difficult to express in terms of key-value
232
+ pairs. Perhaps the key could be an array so that the attribute can be
233
+ included with the key? That would be possible without too much work,
234
+ but how would it look? Does the syntax for search start to get too
235
+ crazy at that point? For instance, you cannot use the label syntax for
236
+ hash keys with an array (unfortunately), so code would look like:
237
+
238
+ window.button([:string_for_range, CFRange.new(0,5)] => 'AXEle')
239
+
240
+ This topic is open to debate, but I will always play the part of the
241
+ devil's advocate. :)
242
+
243
+ ### Inference
244
+
245
+ There is also space for enhancement in the attribute inference
246
+ feature(s). Wouldn't it be nice to be able to specify a filter value
247
+ as a range instead of a specific value? Probably not very often, but it is
248
+ something that could be done. Search filtering is not very flexible in
249
+ that regard right now, and maybe it never needs to be, but it is an
250
+ interesting change to think about.
@@ -0,0 +1,52 @@
1
+ # Test Helpers
2
+
3
+ @todo Pretend that this does not exist yet
4
+
5
+ There are some types of assertions that you would like to make during
6
+ testing that simply does not make sense using the build in
7
+ assertions. These can include things like existence checks which you
8
+ would have to implement by searching, or ... that is really the only
9
+ one I have so far :)
10
+
11
+ ## RSpec
12
+
13
+ RSpec 2 is the Marketcircle choice for implementing functional and
14
+ behavioural tests for apps using AXElements. It is great and offers
15
+ the flexibility and features that make using it very good for large
16
+ test suites.
17
+
18
+ You can load the RSpec matchers that AXElements adds by requiring
19
+
20
+ require 'rspec/ax_elements'
21
+
22
+ ### Existence
23
+
24
+ Checking for the existence of an object in RSpec with AXElements looks
25
+ a bit awkward:
26
+
27
+ window.search(:button).should be_empty
28
+ # or
29
+ window.search(:button).should_not be_empty
30
+
31
+ That does not communicate intent as clearly as it could. What if you
32
+ could say something like:
33
+
34
+ window.should have_a :button
35
+ # or
36
+ window.should_not have_a :button
37
+
38
+ What if you have filters? Well, that is handled as well, though maybe
39
+ not as nicely:
40
+
41
+ window.should have_a(:button).with(title: 'Hello')
42
+
43
+ ## Minitest
44
+
45
+ AXElements uses minitest for its own regression test suite. Minitest
46
+ is pretty cool, and there is good cause to support it as well. To that
47
+ end, we have provided the equivalent assertions.
48
+
49
+ You can load it like so:
50
+
51
+ require 'minitest/ax_elements'
52
+
Binary file
Binary file
@@ -0,0 +1,34 @@
1
+ digraph {
2
+ Application [label = "Application: Mail", style=filled]
3
+ MainWindow [label = "MainWindow: Inbox"]
4
+ Outline [label = "Outline"]
5
+ Table [label = "Table: Emails"]
6
+ TableRow1 [label = "TableRow: Spam"]
7
+ TableRow2 [label = "TableRow: Daily LOLcat"]
8
+ MenuBar [label = "MenuBar", style=filled]
9
+ MenuBarItem1 [label = "MenuBarItem: File", style=filled]
10
+ MenuBarItem2 [label = "MenuBarItem: Edit"]
11
+ MenuBarItem3 [label = "MenuBarItem: Help"]
12
+ Menu1 [label = "Menu", style=filled]
13
+ MenuItem1 [label = "MenuItem: Preferences"]
14
+ MenuItem2 [label = "MenuItem: Services", style=filled]
15
+ MenuItem3 [label = "MenuItem: Quit"]
16
+ Menu2 [label = "Menu", style=filled]
17
+ MenuItem4 [label = "MenuItem: Services Preferences", style=filled]
18
+
19
+ Application -> MainWindow
20
+ Application -> MenuBar
21
+ MainWindow -> Outline
22
+ MainWindow -> Table
23
+ Table -> TableRow1
24
+ Table -> TableRow2
25
+ MenuBar -> MenuBarItem1
26
+ MenuBar -> MenuBarItem2
27
+ MenuBar -> MenuBarItem3
28
+ MenuBarItem1 -> Menu1
29
+ Menu1 -> MenuItem1
30
+ Menu1 -> MenuItem2
31
+ Menu1 -> MenuItem3
32
+ MenuItem2 -> Menu2
33
+ Menu2 -> MenuItem4
34
+ }
Binary file
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ $CFLAGS << ' -std=c99 -fobjc-gc -Wall -Werror'
4
+ $LIBS << ' -framework Cocoa -framework CoreServices -framework Carbon'
5
+
6
+ create_makefile('key_coder')