AXElements 0.7.8 → 0.8.0

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 (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.