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.
- data/.yardopts +20 -0
- data/LICENSE.txt +25 -0
- data/README.markdown +150 -0
- data/Rakefile +109 -0
- data/docs/AccessibilityTips.markdown +119 -0
- data/docs/Acting.markdown +340 -0
- data/docs/Debugging.markdown +326 -0
- data/docs/Inspecting.markdown +255 -0
- data/docs/KeyboardEvents.markdown +57 -0
- data/docs/NewBehaviour.markdown +151 -0
- data/docs/Notifications.markdown +271 -0
- data/docs/Searching.markdown +250 -0
- data/docs/TestingExtensions.markdown +52 -0
- data/docs/images/AX.png +0 -0
- data/docs/images/all_the_buttons.jpg +0 -0
- data/docs/images/ui_hierarchy.dot +34 -0
- data/docs/images/ui_hierarchy.png +0 -0
- data/ext/key_coder/extconf.rb +6 -0
- data/ext/key_coder/key_coder.m +77 -0
- data/lib/ax_elements/accessibility/enumerators.rb +104 -0
- data/lib/ax_elements/accessibility/language.rb +347 -0
- data/lib/ax_elements/accessibility/qualifier.rb +73 -0
- data/lib/ax_elements/accessibility.rb +164 -0
- data/lib/ax_elements/core.rb +541 -0
- data/lib/ax_elements/element.rb +593 -0
- data/lib/ax_elements/elements/application.rb +88 -0
- data/lib/ax_elements/elements/button.rb +18 -0
- data/lib/ax_elements/elements/radio_button.rb +18 -0
- data/lib/ax_elements/elements/row.rb +30 -0
- data/lib/ax_elements/elements/static_text.rb +17 -0
- data/lib/ax_elements/elements/systemwide.rb +46 -0
- data/lib/ax_elements/inspector.rb +116 -0
- data/lib/ax_elements/macruby_extensions.rb +255 -0
- data/lib/ax_elements/notification.rb +37 -0
- data/lib/ax_elements/version.rb +9 -0
- data/lib/ax_elements.rb +30 -0
- data/lib/minitest/ax_elements.rb +19 -0
- data/lib/mouse.rb +185 -0
- data/lib/rspec/expectations/ax_elements.rb +15 -0
- data/test/elements/test_application.rb +72 -0
- data/test/elements/test_row.rb +27 -0
- data/test/elements/test_systemwide.rb +38 -0
- data/test/helper.rb +119 -0
- data/test/test_accessibility.rb +127 -0
- data/test/test_blankness.rb +26 -0
- data/test/test_core.rb +448 -0
- data/test/test_element.rb +939 -0
- data/test/test_enumerators.rb +81 -0
- data/test/test_inspector.rb +121 -0
- data/test/test_language.rb +157 -0
- data/test/test_macruby_extensions.rb +303 -0
- data/test/test_mouse.rb +5 -0
- data/test/test_search_semantics.rb +143 -0
- metadata +219 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
# Inspecting The User Interface
|
2
|
+
|
3
|
+
When it comes to inspecting the user interface there are _many_ nooks
|
4
|
+
and crannies that are interesting to talk about. This document covers
|
5
|
+
the core concepts that you will need to understand before you can
|
6
|
+
begin discovering them yourself.
|
7
|
+
|
8
|
+
## The UI Tree
|
9
|
+
|
10
|
+
The first most important thing to understand is that accessibility
|
11
|
+
exposes the user interface as a hierarchy of user interface
|
12
|
+
tokens. Each token references a GUI element on screen, either
|
13
|
+
something literal like a button, or something more structural like a
|
14
|
+
group of buttons. For simplicity, I will refer to tokens as if they were the
|
15
|
+
objects themselves.
|
16
|
+
|
17
|
+
Each object knows who its parent object is, and knows about any
|
18
|
+
children objects that it may have. thus creating a tree structure. At
|
19
|
+
least this is the theory, but there are, on occasion, some hiccups
|
20
|
+
since accessibility is a protocol to which multiple parties have to
|
21
|
+
conform.
|
22
|
+
|
23
|
+
A sample hierarchy might start with the application, which has a menu
|
24
|
+
bar as one of its children and the menu bar has menu bar items, each
|
25
|
+
menu bar item has a menu and each menu has menu items; some menu items
|
26
|
+
have another menu as its child which then leads to more menu items and
|
27
|
+
so on. This hierarchy is much easier to understand once visualized:
|
28
|
+
|
29
|
+

|
30
|
+
|
31
|
+
This example is meant to be instructive, the menu bar is one of the
|
32
|
+
more complicated hierarchies to navigate and many other nodes in the
|
33
|
+
hierarchy have been left out. The good news is that AXElements has
|
34
|
+
techniques and patterns for simplifying navigation, so don't get
|
35
|
+
scared off just yet. The point here is that each menu item knows which
|
36
|
+
menu is its parent, and each menu knows which menu item or menu bar
|
37
|
+
item is its parent, and the menu bar knows that which application is
|
38
|
+
its parent. But who is the parent of the application? It turns out
|
39
|
+
that that is a trick question, an application does not have a
|
40
|
+
parent. An application is the entry point for the UI hierarchy, it
|
41
|
+
will be the place where a script usually starts and application
|
42
|
+
objects can be created using the {Accessibility} singleton
|
43
|
+
methods. You can create the object for an application that is already
|
44
|
+
running using {Accessibility.application_with_name} like so:
|
45
|
+
|
46
|
+
app = Accessibility.application_with_name = 'Finder'
|
47
|
+
|
48
|
+
### Accessibility Inspector
|
49
|
+
|
50
|
+
To quickly navigate through the UI tree, Apple has provided a tool,
|
51
|
+
the Accessibility Inspector, as part of the Developer Tools. The
|
52
|
+
inspector will come in handy when you are writing scripts using
|
53
|
+
AXElements, though there are some potential pitfalls that will be
|
54
|
+
discussed later.
|
55
|
+
|
56
|
+
Once you install the Developer Tools, the inspector can be found in
|
57
|
+
`/Developer/Applications/Utilities/Accessibility Tools/`. It is worth
|
58
|
+
playing around with the inspector to get a feel for what the
|
59
|
+
accessibility APIs offer; but keep in mind that the inspector is a
|
60
|
+
dumb interface to the accessibility APIs.
|
61
|
+
|
62
|
+
## Attributes
|
63
|
+
|
64
|
+
Each item in the GUI tree has attributes; buttons have a title,
|
65
|
+
sliders have a value, etc.. Pointers to the parent and chilrden nodes
|
66
|
+
are also attributes. Programmatically, you can get a list of
|
67
|
+
attributes that an object has by asking nicely. AXElements exposes
|
68
|
+
this API via {AX::Element#attributes}. {AX::Element} actually acts as
|
69
|
+
the abstract base class for all objects, encapsulating everything that
|
70
|
+
the accessibility APIs offer.
|
71
|
+
|
72
|
+
### Accessing Attributes
|
73
|
+
|
74
|
+
Every attribute can be accessed as a method of the UI object. The
|
75
|
+
method name will always be the
|
76
|
+
[snake_case](http://en.wikipedia.org/wiki/Letter_case) version of the
|
77
|
+
attribute name without the prefix.
|
78
|
+
|
79
|
+
Some examples:
|
80
|
+
|
81
|
+
- `AXChildren` would become `children`
|
82
|
+
- `AXMainWindow` would become `main_window`
|
83
|
+
- `AXIsApplicationRunning` would become `application_running?`
|
84
|
+
|
85
|
+
The last case is special because we consider "`Is`" to be part of the
|
86
|
+
prefix and the method name has a "`?`" at the end. This is to follow
|
87
|
+
Ruby conventions of putting "`?`" at the end of the method name if the
|
88
|
+
method is a predicate. There will be more details about these rules in
|
89
|
+
other tutorial documents, but this is really something that should be
|
90
|
+
abstracted away. This detail is not hidden right now becaues the
|
91
|
+
Accessibility Inspector does not hide the information and you still
|
92
|
+
need to understand it in order to use the inspector with AXElements.
|
93
|
+
|
94
|
+
#### Example
|
95
|
+
|
96
|
+
We can demonstrate how this all comes together with a small
|
97
|
+
example. In the terminal, you can start up a console session of
|
98
|
+
AXElements by loading `ax_elements` in `macirb` or by navigating to
|
99
|
+
the AXElements repository and running the `console` task if you have a
|
100
|
+
clone. Then you can try the following code, one line at a time:
|
101
|
+
|
102
|
+
app = Accessibility.application_with_name 'Terminal'
|
103
|
+
window = app.main_window
|
104
|
+
title = window.title
|
105
|
+
puts "The window's title is '#{title}'"
|
106
|
+
|
107
|
+
In the first line, we are creating the object for the application. As
|
108
|
+
mentioned earlier, you will usually start navigating the UI tree from
|
109
|
+
the application object. Giving the name of the application is the
|
110
|
+
easiest way to create an application object but requires the
|
111
|
+
application to already be running.
|
112
|
+
|
113
|
+
On the second line we use the application object and ask it for the
|
114
|
+
value of the `main_window` attribute. This will return to us another
|
115
|
+
UI object, this time for the window. You will also notice that the
|
116
|
+
console printed out some extra information about the window, such as
|
117
|
+
the title of the window and its position (in flipped
|
118
|
+
coordinates). Each UI element has implemented the `#inspect` method in
|
119
|
+
such a way as to provide users with a succinct but useful way to
|
120
|
+
identify the UI element on the screen, and `macirb` is designed to
|
121
|
+
happily print that information out for each statement that you enter.
|
122
|
+
|
123
|
+
On the third line, we ask the window for it's `title`, and it gives
|
124
|
+
us back a string which we then print out on the fourth line. Notice
|
125
|
+
that the title of the window was also printed by the console as part
|
126
|
+
of the `#inspect` output that `macirb` asks to print out.
|
127
|
+
|
128
|
+
### Inspect Output
|
129
|
+
|
130
|
+
Using `#inspect` is a great way to see the important details of a UI
|
131
|
+
element, it shows the values of the most important attributes so that
|
132
|
+
you can quickly identify which element it really is on screen, but not
|
133
|
+
so many details that it becomes a pain. A typical example of
|
134
|
+
`#inspect` output looks like this:
|
135
|
+
|
136
|
+
#<AX::StandardWindow "AXElementsTester" (1584.0, 184.0) 17 children focused[✘]>
|
137
|
+
|
138
|
+
That output includes all the pieces that you will normally see from
|
139
|
+
`#inspect`, but you may see less or more depending on the UI element
|
140
|
+
that is being inspected. As is the norm in Ruby, you will always at
|
141
|
+
least get the name of the class; then AXElements will try to include a
|
142
|
+
piece of identifying information such as the `title`, then you have
|
143
|
+
numbers in parentheses which are the screen coordinates for the
|
144
|
+
object, then you have the number of children, and then check boxes for
|
145
|
+
boolean attributes. Aside from the class name, the other pieces of
|
146
|
+
information will only be included if they are relevant, and certain
|
147
|
+
objects will also include other information.
|
148
|
+
|
149
|
+
The values shown by `#inspect` are pieced together using helper
|
150
|
+
methods from the {Accessibility::PPInspector} module. {AX::Element}
|
151
|
+
implements a generic implementation with
|
152
|
+
{AX::Element#inspect}. However, the generic `#inspect` may not always
|
153
|
+
choose the best attributes to show. An example would be
|
154
|
+
{AX::Application#inspect}, which overrides the generic `inspect` so
|
155
|
+
that the process identifier for the application is also included. In
|
156
|
+
other cases, the screen co-ordinates or whether the element is enabled
|
157
|
+
may not be relevant, so you can override the method in the specific
|
158
|
+
subclass to not include those attributes and/or include other
|
159
|
+
attributes. The key idea is to make `#inspect` helpful when exploring
|
160
|
+
a UI through the console or when debugging a script.
|
161
|
+
|
162
|
+
## Accessing Children
|
163
|
+
|
164
|
+
Following first principles shown in the example from above you might
|
165
|
+
be led to believe that in order to navigate around the UI tree you
|
166
|
+
will have to write code that looks something like this:
|
167
|
+
|
168
|
+
app.main_window.children.find do |child|
|
169
|
+
child.class == AX::Button && child.title == 'Add'
|
170
|
+
end
|
171
|
+
|
172
|
+
However, AXElements provides a way to specify what you want that is
|
173
|
+
much more convenient to use. Behold:
|
174
|
+
|
175
|
+
app.main_window.button(title: 'Add')
|
176
|
+
|
177
|
+
This is quite the simplification! If we break it down, you see that
|
178
|
+
the method name is the class of the object you want, and then if you
|
179
|
+
need to be more specific you can pass key-value pairs where the key
|
180
|
+
is an attribute and the value is the expected value. The above example
|
181
|
+
says "find a button with the title of 'Add'".
|
182
|
+
|
183
|
+
You can use as many or as few key-value pairs as you need in order to
|
184
|
+
find the element that you are looking for. If you do not specify any
|
185
|
+
key-value pairs, then the first object with the correct class will be
|
186
|
+
chosen. The {file:docs/Searching.markdown Searching Tutorial} goes
|
187
|
+
into more depth on how key-value pairs are used to specify which
|
188
|
+
object you want.
|
189
|
+
|
190
|
+
## Parameterized Attributes
|
191
|
+
|
192
|
+
There is a special type of attribute that is called the parameterized
|
193
|
+
attribute. The difference from a regular attribute is that you need to
|
194
|
+
supply a parameter. An example of this would look like this:
|
195
|
+
|
196
|
+
static_text.string_for_range CFRange.new(0,5)
|
197
|
+
|
198
|
+
The method name suggests that you need to provide a range and in
|
199
|
+
return you will be given part of the string that corresponds to the
|
200
|
+
range. Of course, this example is quite contrived since string slicing
|
201
|
+
is so trivial in Ruby (but this parameterized attribute actually exists).
|
202
|
+
|
203
|
+
Parameterized attributes are different enough from regular attributes
|
204
|
+
that Apple does not want them mixing together and producing
|
205
|
+
offspring. AXElements is a bit progressive, but still keeps the list
|
206
|
+
of parameterized attributes separate from attributes; you can get a
|
207
|
+
list of parameterized attributes for an object with
|
208
|
+
{AX::Element#param_attributes}. Similarly, you have probably already
|
209
|
+
noticed that parameterized attributes have their own section in the
|
210
|
+
Accessibility Inspector and that many objectss do not have any
|
211
|
+
parameterized attributes.
|
212
|
+
|
213
|
+
In my experience, parameterized attributes have not been very useful,
|
214
|
+
but I haven't looked hard enough and am still looking for a good
|
215
|
+
example to put in this section of the tutorial.
|
216
|
+
|
217
|
+
## Explicit Attribute Access
|
218
|
+
|
219
|
+
In cases where you know what you want is going to be an attribute, you
|
220
|
+
can get better performance from accessing attributes by calling
|
221
|
+
{AX::Element#attribute} and passing the attribute name.
|
222
|
+
|
223
|
+
app.attribute(:main_window)
|
224
|
+
|
225
|
+
Similarly, for parameterized attributes, you need to call
|
226
|
+
{AX::Element#param_attribute} and pass the attribute name and
|
227
|
+
parameter as parameters to that method.
|
228
|
+
|
229
|
+
app.param_attribute(:string_for_range, CFRange.new(0,5))
|
230
|
+
|
231
|
+
These methods are exposed so that other library classes can achieve
|
232
|
+
better performance; but you should avoid using them regularly. These
|
233
|
+
APIs may be hidden in the future in order to enforce the DSL usage.
|
234
|
+
|
235
|
+
## Adding Accessibility Attributes
|
236
|
+
|
237
|
+
You can add custom attributes to objects, or even inject or hide
|
238
|
+
objects from the UI hierarchy. It is simply a matter of
|
239
|
+
overriding/implementing methods from the
|
240
|
+
[NSAccessibility](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Protocols/NSAccessibility_Protocol/Reference/Reference.html)
|
241
|
+
protocol where needed.
|
242
|
+
|
243
|
+
You should peruse the {file:docs/AccessibilityTips.markdown Accessibility Tips}
|
244
|
+
documentation before making nontrivial changes. There are a couple of
|
245
|
+
guidelines you need to be aware of in order to make sure things remain
|
246
|
+
compatible with AXElements.
|
247
|
+
|
248
|
+
## Next Steps
|
249
|
+
|
250
|
+
You may want to play with what you have learnt so far, see if you can
|
251
|
+
find bugs and then fix them, or perhaps add missing features. ;)
|
252
|
+
|
253
|
+
From here the next logical step would be to figure out how to trigger
|
254
|
+
some sort of action and then inspect the UI for changes; for that
|
255
|
+
topic you should read the {file:docs/Acting.markdown Acting Tutorial}.
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Keyboard Events
|
2
|
+
|
3
|
+
@todo Still a little bit to do in this doc
|
4
|
+
|
5
|
+
In some cases you cannot, or do not want to, set the value of a field
|
6
|
+
directly by assigning to the accessibility object's value
|
7
|
+
attribute. In these cases you can post keyboard events to an
|
8
|
+
application object.
|
9
|
+
|
10
|
+
Other cases for using keyboard input simulation might include keyboard
|
11
|
+
navigation or hotkey activation.
|
12
|
+
|
13
|
+
The DSL exposes Keyboard events through the `type` keyword. An example
|
14
|
+
would look like this:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
type "hello, world!", app
|
18
|
+
```
|
19
|
+
|
20
|
+
In order make sure that certain sequences of characters are properly
|
21
|
+
transcribed to the screen you should be consistent about using double
|
22
|
+
quotes, `"`, for literal strings. An escape sequence should begin
|
23
|
+
`\\`.
|
24
|
+
|
25
|
+
First the keyword `type`, and then the string you want to type
|
26
|
+
out. Finally, there is a second argument, which is optional, that
|
27
|
+
should be the application that you wish to send the keyboard input
|
28
|
+
to; if you do not include the argument, then the input will go to the
|
29
|
+
currently activate application.
|
30
|
+
|
31
|
+
## Behaviour
|
32
|
+
|
33
|
+
The method is asynchronous. It appears to type information in at the
|
34
|
+
keyboard repeat rate, but I have yet to check that out.
|
35
|
+
|
36
|
+
## Escape Sequences
|
37
|
+
|
38
|
+
A number of custom escape sequences have been added in order to allow easy
|
39
|
+
encoding of all key sequences/combinations.
|
40
|
+
|
41
|
+
<table style="1px solid black">
|
42
|
+
<tr><td>Key</td><td>Escape Sequence</td></tr>
|
43
|
+
<tr><td>Delete Backwards (Backspace)</td><td>`\b`</td></tr>
|
44
|
+
<tr><td>Enter</td><td>`\n` or `\r`</td></tr>
|
45
|
+
<tr><td>Control</td><td>`\^`</td></tr>
|
46
|
+
<tr><td>Command</td><td>`\C`</td></tr>
|
47
|
+
<tr><td>Option/Alt</td><td>`\O`</td></tr>
|
48
|
+
<tr><td>Tab</td><td>`\t`</td></tr>
|
49
|
+
<tr><td>Escape</td><td>`\e`</td></tr>
|
50
|
+
<tr><td>Space</td><td>` `</td></tr>
|
51
|
+
<tr><td>Scroll To Top</td><td>`\x01`</td></tr>
|
52
|
+
<tr><td>Scroll To Bottom</td><td>`\x04`</td></tr>
|
53
|
+
<tr><td>Page Down</td><td>`\f`</td></tr>
|
54
|
+
<tr><td>Left Arrow</td><td>`\\<->`</td></tr>
|
55
|
+
<tr><td>Right Arrow</td><td>`\\->`</td></tr>
|
56
|
+
<tr><td>Down Arrow</td><td>`\DOWN`</td></tr>
|
57
|
+
</table>
|
@@ -0,0 +1,151 @@
|
|
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).
|