AXElements 0.7.8 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.yardopts +1 -10
  2. data/README.markdown +7 -14
  3. data/ext/accessibility/key_coder/key_coder.c +7 -0
  4. data/lib/AXElements.rb +0 -2
  5. data/lib/accessibility/core.rb +180 -123
  6. data/lib/accessibility/dsl.rb +310 -191
  7. data/lib/accessibility/enumerators.rb +9 -8
  8. data/lib/accessibility/errors.rb +7 -8
  9. data/lib/accessibility/factory.rb +16 -9
  10. data/lib/accessibility/graph.rb +68 -22
  11. data/lib/accessibility/highlighter.rb +86 -0
  12. data/lib/accessibility/pp_inspector.rb +4 -4
  13. data/lib/accessibility/qualifier.rb +11 -9
  14. data/lib/accessibility/string.rb +12 -4
  15. data/lib/accessibility/translator.rb +19 -10
  16. data/lib/accessibility/version.rb +3 -1
  17. data/lib/accessibility.rb +42 -17
  18. data/lib/ax/application.rb +90 -30
  19. data/lib/ax/button.rb +5 -2
  20. data/lib/ax/element.rb +133 -149
  21. data/lib/ax/pop_up_button.rb +12 -0
  22. data/lib/ax/radio_button.rb +5 -2
  23. data/lib/ax/row.rb +2 -2
  24. data/lib/ax/static_text.rb +5 -2
  25. data/lib/ax/systemwide.rb +24 -12
  26. data/lib/ax_elements/awesome_print.rb +13 -0
  27. data/lib/ax_elements/exception_workaround.rb +5 -0
  28. data/lib/ax_elements/nsarray_compat.rb +1 -0
  29. data/lib/ax_elements.rb +2 -1
  30. data/lib/minitest/ax_elements.rb +60 -4
  31. data/lib/mouse.rb +47 -20
  32. data/lib/rspec/expectations/ax_elements.rb +180 -88
  33. data/rakelib/doc.rake +7 -0
  34. data/test/helper.rb +2 -1
  35. data/test/integration/accessibility/test_dsl.rb +126 -18
  36. data/test/integration/accessibility/test_errors.rb +1 -1
  37. data/test/integration/ax/test_element.rb +17 -0
  38. data/test/integration/minitest/test_ax_elements.rb +33 -38
  39. data/test/integration/rspec/expectations/test_ax_elements.rb +68 -19
  40. data/test/sanity/accessibility/test_core.rb +45 -37
  41. data/test/sanity/accessibility/test_highlighter.rb +56 -0
  42. data/test/sanity/ax/test_application.rb +8 -0
  43. data/test/sanity/ax/test_element.rb +7 -3
  44. data/test/sanity/minitest/test_ax_elements.rb +2 -0
  45. data/test/sanity/rspec/expectations/test_ax_elements.rb +3 -0
  46. data/test/sanity/test_accessibility.rb +9 -0
  47. data/test/sanity/test_mouse.rb +2 -2
  48. metadata +11 -38
  49. data/docs/AccessibilityTips.markdown +0 -119
  50. data/docs/Acting.markdown +0 -340
  51. data/docs/Debugging.markdown +0 -165
  52. data/docs/Inspecting.markdown +0 -261
  53. data/docs/KeyboardEvents.markdown +0 -122
  54. data/docs/NewBehaviour.markdown +0 -151
  55. data/docs/Notifications.markdown +0 -271
  56. data/docs/Searching.markdown +0 -250
  57. data/docs/TestingExtensions.markdown +0 -52
  58. data/docs/images/all_the_buttons.jpg +0 -0
  59. data/docs/images/next_version.png +0 -0
  60. data/docs/images/ui_hierarchy.dot +0 -34
  61. data/docs/images/ui_hierarchy.png +0 -0
  62. data/lib/accessibility/debug.rb +0 -164
  63. data/test/integration/accessibility/test_debug.rb +0 -44
  64. data/test/sanity/accessibility/test_debug.rb +0 -63
@@ -1,151 +0,0 @@
1
- # Adding Behaviour
2
-
3
- Sometimes it is necessary to add extra methods to a UI
4
- element. There are a few cases of this in the AXElements source code
5
- itself, but many more opportunities exist. Unfortunately, extending UI
6
- element classes in AXElements is not totally straightforward. Some of
7
- the implementation details need to be understood before you can
8
- successfully extend AXElements.
9
-
10
- ## Laziness
11
-
12
- In a laziness contest between AXElements and Garfield, AXElements
13
- wins (I assume). A lot of data that AXElements needs to be processed,
14
- such as name translation, and the work for this is delayed until it
15
- needs to be done in order to avoid a very large amount of overhead at
16
- boot time. Not all parts of AXElements are lazy, at least not yet.
17
-
18
- ## Class Hierarchy
19
-
20
- At run time you will have noticed that you were returned objects which
21
- have a class like `AX::StandardWindow`, but you can never find the
22
- definition of the class in the source code. This is because the class
23
- hierarchy is lazily defined.
24
-
25
- ### Deciding The Class Name
26
-
27
- The first thing to understand is the way that AXElements decides what
28
- class to instantiate for a UI element. This is actually pretty
29
- simple. Each UI element has a `role` attribute that is supposed to be
30
- used by assistive applications (read: AXElements) to understand which
31
- attributes will likely be available. This is Apple's hint as to what
32
- kind of class structure to choose.
33
-
34
- However, some UI elements also have a `subrole` attribute. For
35
- instance, `AXStandardWindow` is a subrole attribute that a UI element
36
- can have if it has a `role` of `AXWindow`. Once again, this is a hint
37
- that has been built into the system, and AXElements follows these
38
- hints. Put into object-oriented terms, if an object has a `subrole`,
39
- then that `subrole` becomes the class for the object and the `role`
40
- becomes the superclass; if the object does not have a `subrole`, then
41
- the class of the object will be decided by the `role`.
42
-
43
- ### Abstract Base
44
-
45
- In either case, the {AX::Element} class will be an ancestor for the
46
- class that is chosen. A class that is its `subrole` will always have a
47
- superclass that is its `role`, and a class that is a `role` will
48
- always have {AX::Element} as its superclass.
49
-
50
- The advantage to creating this hierarchy is that it becomes much
51
- easier to implement searches that can find a "kind of" object. For
52
- instance, you can search for `text_fields` and find `AX::TextField`
53
- objects as well as `AX::SecureTextField` objects. This is one of the
54
- more powerful features outlined in the
55
- {file:docs/Searching.markdown Searching tutorial}.
56
-
57
- ### Why Lazy?
58
-
59
- Laziness was chosen as it makes the library more resilient to custom
60
- roles and subroles. However, it also allows the MacRuby run time to
61
- boot faster since it avoids having to load all the different classes
62
- that would need to be defined.
63
-
64
- ## Explicitly Defining Classes
65
-
66
- Now that you understand how classes are structured it should be very
67
- obvious how you should name your classes and choose your
68
- superclass. However, you could also use this technique to customize
69
- the hierarchy from what Apple has defined. For instance, you could force a
70
- `AXPopUpButton` to be a subclass of `AXButton` even though Apple has
71
- declared them to be separate roles. This may or may not be convenient
72
- depending on what custom methods you wish to add.
73
-
74
- ## Reasons To Add A Custom Method
75
-
76
- For this topic I can only lead by example. Fortunately AXElements has
77
- a few examples to show off. The full list is in
78
- `lib/ax_elements/elements`, but I'll go over a few here.
79
-
80
- ### Application Objects
81
-
82
- {AX::Application} has been extended in a couple of ways. First off, in
83
- order to provide an object oriented interface to sending keyboard
84
- events I added {AX::Application#type_string}.
85
-
86
- However, the big change with {AX::Application} is the merging of
87
- functionality from `NSRunningApplication`. In order to provide methods
88
- to set focus to an application I had to cache the
89
- `NSRunningApplication` instance at initialization and forward some
90
- method calls to that object. For instance, when you call `#set_focus`
91
- and pass an application object, AXElements does not actually using
92
- accessibility to set focus to the application, it uses the
93
- `NSRunningApplication` class internally still support the
94
- functionality in a transparent way.
95
-
96
- ### Overriding `#==`
97
-
98
- The most popular customization to make is to overload `#==` for an
99
- object class that provides a more natural interface, and also one that
100
- makes search much more flexible. There is more than one example in
101
- this case, you could look at {AX::StaticText#==} which allows you
102
- check equality against a string that equal to the `value` attribute
103
- for the static text object. Similarly, {AX::Button#==} was added to
104
- check equality with buttons.
105
-
106
- ### Table Rows
107
-
108
- When working with `AX::Table` objects you may have issues identifying
109
- children that belong to a specific column. Children of table rows, in
110
- my experience, do not have descriptions identifying which column they
111
- belong to. In cases where you have no unique identifier for a child,
112
- such as if you have multiple check boxes, there is no good way to find
113
- a specific check box using the built in search mechanics.
114
-
115
- You could hope that the column order never changes and just use the
116
- index of the children array but that is fragile; or perhaps you actually
117
- know what the order of the columns is to begin with and were able to
118
- keep track of how they changed.
119
-
120
- A much more sane way to identify the child is by identifying the
121
- column that the child belongs to. For instance, a the column for a
122
- table is an `AX::Column` object that usually has a `header` or `title`
123
- attribute which will be unique for the table. For this case,
124
- AXElements includes the {AX::Row#child_in_column} method which
125
- provides something similar to a search but with the few extra steps
126
- that would be necessary to correlate the child to the column and then
127
- return the child that you wanted.
128
-
129
- ### More
130
-
131
- There are likely other cases that I have not come across yet which
132
- would be significantly simplified by a helper method or the merging of
133
- functionality from another class. Don't be afraid to share your
134
- extensions.
135
-
136
- ## Tests
137
-
138
- If you are adding new features to AXElements then you should add tests
139
- for the new features and also make sure that you don't break existing
140
- features without realizing it.
141
-
142
- Running the test suite is covered in the {file:README.markdown}.
143
-
144
- Figuring out the test suite internals may not be easy, there is a bit
145
- of duplication, and something things need better organization. The
146
- test suite isn't well documented (on purpose) so you will have to read
147
- some of the other code to understand how things should work before
148
- writing your own tests. Be careful not to introduce state dependencies
149
- between tests or else you will not have a fun time tracking down why a
150
- certain test seems fail occassionally (which is a problem I had with
151
- notifications tests).
@@ -1,271 +0,0 @@
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.
@@ -1,250 +0,0 @@
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.