AXElements 0.6.0beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Example GUI Hierarchy](images/ui_hierarchy.png)
|
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).
|