AXElements 0.6.0beta1

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 (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')