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
@@ -88,4 +88,12 @@ class TestAXApplication < MiniTest::Unit::TestCase
88
88
  assert_equal running_app.bundleIdentifier, app.bundle_identifier
89
89
  end
90
90
 
91
+ def test_info_plist
92
+ assert_equal 'hmmmmm.icns', app.info_plist['CFBundleIconFile']
93
+ end
94
+
95
+ def test_version
96
+ assert_equal '1.0', app.version
97
+ end
98
+
91
99
  end
@@ -7,6 +7,10 @@ class TestAXElement < MiniTest::Unit::TestCase
7
7
  @element ||= AX::Element.new REF
8
8
  end
9
9
 
10
+ def test_ancestry
11
+ assert_equal [element], element.ancestry
12
+ end
13
+
10
14
  def test_children # not much we can do here
11
15
  assert_respond_to element, :children
12
16
  assert_kind_of Array, element.children
@@ -38,12 +42,12 @@ class TestAXElement < MiniTest::Unit::TestCase
38
42
  def center_test position, size, expected
39
43
  element.define_singleton_method :attribute do |attr|
40
44
  case attr
41
- when :position then CGPointMake(*position)
42
- when :size then CGSizeMake(*size)
45
+ when :position then CGPoint.new(*position)
46
+ when :size then CGSize.new(*size)
43
47
  else raise ArgumentError
44
48
  end
45
49
  end
46
- assert_equal CGPointMake(*expected), element.to_point
50
+ assert_equal CGPoint.new(*expected), element.to_point
47
51
  end
48
52
 
49
53
  # the nil case
@@ -7,9 +7,11 @@ class TestMiniTestAssertions < MiniTest::Unit::TestCase
7
7
  assert_respond_to self, :assert_has_child
8
8
  assert_respond_to self, :assert_has_descendent
9
9
  assert_respond_to self, :assert_has_descendant
10
+ assert_respond_to self, :assert_shortly_has
10
11
  assert_respond_to self, :refute_has_child
11
12
  assert_respond_to self, :refute_has_descendent
12
13
  assert_respond_to self, :refute_has_descendant
14
+ assert_respond_to self, :refute_shortly_has
13
15
  end
14
16
 
15
17
  end
@@ -7,6 +7,9 @@ class TestRSpecMatchers < MiniTest::Unit::TestCase
7
7
  assert TopLevel.method(:have_child)
8
8
  assert TopLevel.method(:have_descendent)
9
9
  assert TopLevel.method(:have_descendant)
10
+ assert TopLevel.method(:shortly_have_child)
11
+ assert TopLevel.method(:shortly_have_descendent)
12
+ assert TopLevel.method(:shortly_have_descendant)
10
13
  end
11
14
 
12
15
  end
@@ -0,0 +1,9 @@
1
+ require 'test/helper'
2
+ require 'accessibility'
3
+
4
+ class TestAccessibilityDebug < MiniTest::Unit::TestCase
5
+ def test_debug_setting
6
+ assert_respond_to Accessibility, :debug?
7
+ assert_respond_to Accessibility, :debug=
8
+ end
9
+ end
@@ -7,11 +7,11 @@ class TestMouseModule < MiniTest::Unit::TestCase
7
7
  end
8
8
 
9
9
  def test_move_to
10
- point = CGPointMake(100, 100)
10
+ point = CGPoint.new(100, 100)
11
11
  Mouse.move_to point
12
12
  assert_in_delta 0, distance(point,Mouse.current_position), 1.0
13
13
 
14
- point = CGPointMake(rand(700)+150, rand(500)+100)
14
+ point = CGPoint.new(rand(700)+150, rand(500)+100)
15
15
  Mouse.move_to point
16
16
  assert_in_delta 0, distance(point,Mouse.current_position), 1.0
17
17
  end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: AXElements
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.7.8
5
+ version: 0.8.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mark Rada
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-13 00:00:00 Z
12
+ date: 2012-04-24 00:00:00 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -70,30 +70,15 @@ email: mrada@marketcircle.com
70
70
  executables: []
71
71
  extensions:
72
72
  - ext/accessibility/key_coder/extconf.rb
73
- extra_rdoc_files:
74
- - docs/AccessibilityTips.markdown
75
- - docs/Acting.markdown
76
- - docs/Debugging.markdown
77
- - docs/images/all_the_buttons.jpg
78
- - docs/images/next_version.png
79
- - docs/images/ui_hierarchy.dot
80
- - docs/images/ui_hierarchy.png
81
- - docs/Inspecting.markdown
82
- - docs/KeyboardEvents.markdown
83
- - docs/NewBehaviour.markdown
84
- - docs/Notifications.markdown
85
- - docs/Searching.markdown
86
- - docs/TestingExtensions.markdown
87
- - .yardopts
88
- - README.markdown
73
+ extra_rdoc_files: []
89
74
  files:
90
75
  - lib/accessibility/core.rb
91
- - lib/accessibility/debug.rb
92
76
  - lib/accessibility/dsl.rb
93
77
  - lib/accessibility/enumerators.rb
94
78
  - lib/accessibility/errors.rb
95
79
  - lib/accessibility/factory.rb
96
80
  - lib/accessibility/graph.rb
81
+ - lib/accessibility/highlighter.rb
97
82
  - lib/accessibility/pp_inspector.rb
98
83
  - lib/accessibility/qualifier.rb
99
84
  - lib/accessibility/string.rb
@@ -103,6 +88,7 @@ files:
103
88
  - lib/ax/application.rb
104
89
  - lib/ax/button.rb
105
90
  - lib/ax/element.rb
91
+ - lib/ax/pop_up_button.rb
106
92
  - lib/ax/radio_button.rb
107
93
  - lib/ax/row.rb
108
94
  - lib/ax/static_text.rb
@@ -125,8 +111,9 @@ files:
125
111
  - rakelib/gem.rake
126
112
  - rakelib/test.rake
127
113
  - Rakefile
114
+ - README.markdown
115
+ - .yardopts
128
116
  - test/integration/accessibility/test_core.rb
129
- - test/integration/accessibility/test_debug.rb
130
117
  - test/integration/accessibility/test_dsl.rb
131
118
  - test/integration/accessibility/test_enumerators.rb
132
119
  - test/integration/accessibility/test_errors.rb
@@ -138,10 +125,10 @@ files:
138
125
  - test/integration/minitest/test_ax_elements.rb
139
126
  - test/integration/rspec/expectations/test_ax_elements.rb
140
127
  - test/sanity/accessibility/test_core.rb
141
- - test/sanity/accessibility/test_debug.rb
142
128
  - test/sanity/accessibility/test_dsl.rb
143
129
  - test/sanity/accessibility/test_errors.rb
144
130
  - test/sanity/accessibility/test_factory.rb
131
+ - test/sanity/accessibility/test_highlighter.rb
145
132
  - test/sanity/accessibility/test_pp_inspector.rb
146
133
  - test/sanity/accessibility/test_qualifier.rb
147
134
  - test/sanity/accessibility/test_string.rb
@@ -153,24 +140,10 @@ files:
153
140
  - test/sanity/ax_elements/test_nsobject_inspect.rb
154
141
  - test/sanity/minitest/test_ax_elements.rb
155
142
  - test/sanity/rspec/expectations/test_ax_elements.rb
143
+ - test/sanity/test_accessibility.rb
156
144
  - test/sanity/test_ax_elements.rb
157
145
  - test/sanity/test_mouse.rb
158
146
  - test/helper.rb
159
- - docs/AccessibilityTips.markdown
160
- - docs/Acting.markdown
161
- - docs/Debugging.markdown
162
- - docs/images/all_the_buttons.jpg
163
- - docs/images/next_version.png
164
- - docs/images/ui_hierarchy.dot
165
- - docs/images/ui_hierarchy.png
166
- - docs/Inspecting.markdown
167
- - docs/KeyboardEvents.markdown
168
- - docs/NewBehaviour.markdown
169
- - docs/Notifications.markdown
170
- - docs/Searching.markdown
171
- - docs/TestingExtensions.markdown
172
- - .yardopts
173
- - README.markdown
174
147
  homepage: http://github.com/Marketcircle/AXElements
175
148
  licenses:
176
149
  - BSD 3-clause
@@ -198,7 +171,6 @@ specification_version: 3
198
171
  summary: A DSL for automating GUI manipulation
199
172
  test_files:
200
173
  - test/integration/accessibility/test_core.rb
201
- - test/integration/accessibility/test_debug.rb
202
174
  - test/integration/accessibility/test_dsl.rb
203
175
  - test/integration/accessibility/test_enumerators.rb
204
176
  - test/integration/accessibility/test_errors.rb
@@ -210,10 +182,10 @@ test_files:
210
182
  - test/integration/minitest/test_ax_elements.rb
211
183
  - test/integration/rspec/expectations/test_ax_elements.rb
212
184
  - test/sanity/accessibility/test_core.rb
213
- - test/sanity/accessibility/test_debug.rb
214
185
  - test/sanity/accessibility/test_dsl.rb
215
186
  - test/sanity/accessibility/test_errors.rb
216
187
  - test/sanity/accessibility/test_factory.rb
188
+ - test/sanity/accessibility/test_highlighter.rb
217
189
  - test/sanity/accessibility/test_pp_inspector.rb
218
190
  - test/sanity/accessibility/test_qualifier.rb
219
191
  - test/sanity/accessibility/test_string.rb
@@ -225,6 +197,7 @@ test_files:
225
197
  - test/sanity/ax_elements/test_nsobject_inspect.rb
226
198
  - test/sanity/minitest/test_ax_elements.rb
227
199
  - test/sanity/rspec/expectations/test_ax_elements.rb
200
+ - test/sanity/test_accessibility.rb
228
201
  - test/sanity/test_ax_elements.rb
229
202
  - test/sanity/test_mouse.rb
230
203
  - test/helper.rb
@@ -1,119 +0,0 @@
1
- # Accessibility Tips
2
-
3
- This document includes tips for customizing accessibility in your own
4
- applications. The goal is to inform you of pitfalls that are not
5
- mentioned in Apple's documentation. It also includes notes on how to
6
- avoid making decsions that will make an application incompatible with
7
- AXElements.
8
-
9
- @todo This document is under construction and is currently just a set
10
- of notes and unorganized sections ripped from other documents.
11
-
12
- ## Guidelines
13
-
14
- When implementing custom accessibility roles, you should never use a
15
- pluralized name for the role. A role that is already pluralized will
16
- break search.
17
-
18
-
19
- ## Adding new stuff
20
-
21
- Adding new attributes to an object is very simple...usually. Most of
22
- the time it is as simple as overriding two methods:
23
-
24
- - `accessibilityAttributeNames`
25
- - `accessibilityAttributeValue:`
26
-
27
- `accessibilityAttributeNames` needs to return an array with the names
28
- of available attributes. You should call `super` to get the existing
29
- array first, and then append any custom attributes you want to
30
- provide.
31
-
32
- An attribute name must follow the convention of being a camel cased
33
- string with the "AX" prefix, such as `AXTitle`. You can optionally
34
- include an additional prefix before "AX", such as "MCAX" for
35
- Marketcircle custom attributes. If you do not follow these rules then
36
- attribute names will not be translated properly by AXElements.
37
-
38
- `accessibilityAttributeValue:` is how you actually provide the value
39
- for the attribute; the parameter for this method is the name of the
40
- attribute fetched from calling `accessibilityAttributeNames`. You
41
- should return the value without doing any extra work to the data; the
42
- Accessibility interface will do any wrapping or translating for
43
- you (e.g. CGPoint objects will have co-ordinates flipped). In the case
44
- of a C structs you should leave them wrapped in an NSValue object.
45
-
46
- Similarly, parameterized attributes are added using methods with
47
- `Parameter` in the name. The method for getting the value of a
48
- parameterized attribute will of course take an extra parameter for the
49
- parameterized attributes parameter.
50
-
51
- ### Where To Implement The Methods
52
-
53
- The difficulty in adding attributes lies in finding out where you need
54
- to add the accessibility methods. Often, the class that implements
55
- accessibility is the class that draws the user interface element. For
56
- example, the accessibility information for a button is implemented on
57
- the button cell and not the button itself. Since Apple is under the
58
- delusion that you will never need to add any custom accessibility
59
- information, they really don't document these customizations enough,
60
- and so this document only includes caveats that have been discovered
61
- so far.
62
-
63
- It is often inconvenient to subclass the cell for an object just for
64
- accessibility. In these cases Apple has provided something similar to
65
- singleton methods, but less useful, with the
66
- `accessibilitySetOverrideValue:forAttribute:` method. You can use the
67
- method to override an attribute or even add a new one. The main issue
68
- with this method is that any attribute that is overridden, or added,
69
- will not be writable using the accessibility APIs. Another issue is
70
- that you have to calculate the value when you override instead of when
71
- the attribute value is queried by the client.
72
-
73
- ### Writability
74
-
75
- In the case where it is convenient to be able to change an attribute's
76
- value through accessibility, like the size of a window, you will also
77
- need to implement two more methods for the attribute:
78
-
79
- - `accessibilityIsAttributeSettable:`
80
- - `accessibilitySetValue:forAttribute:`
81
-
82
- `accessibilityIsAttributeSettable:` simply responds with whether or
83
- not the attribute is writable, and
84
- `accessibilitySetValue:forAttribute:` will be called to actually write
85
- to the attribute.
86
-
87
- ### Remember `super`
88
-
89
- It is important to remember that these methods are already implemented
90
- for the built in features. When overriding, you should only implement
91
- the custom behaviour and call `super` to handle everything else.
92
-
93
- ### Existing Definitions
94
-
95
- When implementing custom behaviour, you should try and use
96
- pseudoclasses that have already been defined by Apple, as well as
97
- attributes, parameterized attributes, and other features that have
98
- already been defined. Apple maintains the documentation for all their
99
- definitions on
100
- [here](http://developer.apple.com/library/mac/#documentation/UserExperience/Reference/Accessibility_RoleAttribute_Ref/Introduction.html#//apple_ref/doc/uid/TP40007870).
101
- The documentation is fairly detailed now (moreso than before), but
102
- still misses a few things.
103
-
104
-
105
- ## Adding Accessibility Actions
106
-
107
- If you have access to the source code for an app, you can add more
108
- accessibility actions. You need to override two methods, just like
109
- with adding attributes:
110
-
111
- – `accessibilityActionNames`
112
- – `accessibilityPerformAction:`
113
-
114
- These methods should be implemented in the same way that you would
115
- implement new attributes, just as detailed in the
116
- {file:docs/Inspecting.markdown Inspecting tutorial}. The one
117
- difference is that `accessibilityPerformAction:` should not return
118
- anything after it performs the action.
119
-
data/docs/Acting.markdown DELETED
@@ -1,340 +0,0 @@
1
- # Actions (a.k.a. The Fun Part)
2
-
3
- Actions are how you drive the user interface. They can be anything
4
- from showing a menu to dragging a window, but are often simple things
5
- like pressing a button. Most actions are subtly different cases
6
- of the same thing, but it is important to understand the differences
7
- and when to use different actions.
8
-
9
- There are three types of actions, those provided by the accessibility
10
- API as direct calls, those provided by accessibility APIs as keyboard
11
- simulation, and those that are provided by the CGEvents API. In most
12
- cases you can use at least two types of actions to achieve the desired
13
- effect and each type of action has its own pros and cons to consider.
14
-
15
- ## Interface
16
-
17
- Though there are three different APIs under the hood, they are exposed
18
- through a common interface via the DSL methods in
19
- {Accessibility::Language}.
20
-
21
- app = Accessibility.application_with_name 'Terminal'
22
- press app.main_window.minimize_button
23
- type "\\CMD+M", app
24
- click app.main_window.minimize_button
25
-
26
- The above example demonstrates performing actions using the DSL
27
- methods. The DSL methods are always some kind of action, such as
28
- {Accessibility::Language#drag_mouse_to drag\_mouse\_to}, or
29
- {Accessibility::Language#set_focus set\_focus} to name two.
30
-
31
- The point of the DSL is to put the actions at the front of statements
32
- in order to separate verbs from nouns. In this way, scripts should
33
- resemble instructions that you might give for reproduction steps in a
34
- bug report---they should read like they were instructions for
35
- a person. This style should make it easier to think about writing
36
- scripts and it should also give better hints at when to refactor
37
- code into helper methods like `log_in_as:with_password:`.
38
-
39
- If you tried out the code snippet above you might notice the
40
- difference between the APIs (you also might notice that the second
41
- command does not work, it is a work in progress :(). In the first
42
- case, `press` was using the actions provided by the accessibility APIs
43
- to call the press action on the minimize button for the window, in the
44
- second case we used `type` to type in the hot key for minimizing a
45
- window, and in the third case we used `click` from the CGEvents APIs
46
- to actually move the mouse cursor to the button and generate a click
47
- event.
48
-
49
- ## Accessibility Actions
50
-
51
- Actions like `press` use the accessibility APIs and show up in the
52
- list of actions returned by {AX::Element#actions}. These actions will
53
- also show up in the Accessibility Inspector in the Actions section.
54
-
55
- You may have noticed that {Accessibility::Language#press press}
56
- shows up as `AXPress` for buttons when you view them with the
57
- Accessibility Inspector or by calling {AX::Element#actions}. This is
58
- because we do name translation with accessibility actions just like
59
- when accessing attributes as shown in the
60
- {file:docs/Inspecting.markdown Inspection tutorial}.
61
-
62
- Accessibility actions are the simplest and fastest way to trigger
63
- actions. They use the accessibility APIs to talk directly to the
64
- application to call the same methods that would be called when
65
- you interact with the application with the mouse or keyboard, but
66
- without having to use the mouse or keyboard.
67
-
68
- ## Setting Attributes
69
-
70
- Though it is not really an action, some attributes can have their
71
- values changed through the accessibility APIs. The
72
- {Accessibility::Language} module has two methods, `set` and
73
- `set_focus`, which allow you to change the value for certain
74
- attributes.
75
-
76
- In the Accessibility Inspector, a writable attribute will have `(W)`
77
- next to the attribute name, and the programmatic way to tell if an
78
- attribute is writable is to call {AX::Element#attribute_writable?} and
79
- pass the name of the attribute as a parameter:
80
-
81
- app.main_window.attribute_writable? :size
82
- app.main_window.attribute_writable? :title
83
-
84
- You can only set an attribute if it is writable. When you have an
85
- attribute that is writable, and you would like to change it, then you
86
- simply need to call `set`:
87
-
88
- set app.main_window, size: [500, 500].to_size
89
-
90
- The first parameter is the element, and the second parameter is a
91
- key-value pair with the attribute as the key and the value as the
92
- new value.
93
-
94
- `set_focus` is just syntactic sugar for `set` where the key-value is
95
- set for you and the only parameter you provide is the element you want
96
- to set focus to:
97
-
98
- set_focus app.main_window.text_field
99
-
100
- Though, `set` itself has a special case. The second most frequently
101
- changed attribute for a element is the `value`, it is used almost as
102
- much as `focused`. For this case, you can pass anything that is not a
103
- key-value pair to `set` and it will be assumed that you want to change
104
- the value of the `value` attribute:
105
-
106
- set app.main_window.text_field, 'Mark Rada'
107
-
108
- Another important detail that you might be curious about is that we
109
- called `to_size` on the first snippet showing how to set an
110
- attribute. The developers of MacRuby have added a feature that allows
111
- you to pass an array as an argument when you would normally be
112
- expected to pass a structure such as a `CGPoint`. Since AXElements
113
- APIs inherently need to handle any type of object, it is not sane to try
114
- and do the same type of run time type analysis. In its place, I have
115
- provided some convenience methods for {NSArray arrays} to let you
116
- quickly transform them to the appropriate type of object. In my
117
- opinion, you should just use the proper type of object in the first
118
- place and avoid the overhead.
119
-
120
- ### You Want To Use Actions
121
-
122
- The advantage to using accessibility actions is that they are usually
123
- much easier to use than the other APIs. Accessibility actions are
124
- usually synchronous and so your scripts will not need to worry about
125
- waiting for animations to complete. The only complication is that
126
- sometimes an action is not synchronous and there is no good way to
127
- tell if an action is synchronous without looking at the underlying
128
- code.
129
-
130
- ### Asynchronous Problems
131
-
132
- Problems with synchronous behaviour will occur with all the types of
133
- actions, but less often with accessibility actions, so you should
134
- always try thing out first in the console to make sure. A good rule of
135
- thumb is that anything that looks like it would take a noticeable
136
- amount of time, and is not an animation, will probably be
137
- asynchronous. An example of this would be having to load data from a
138
- database (read: separate process, across a network, etc.) such as in
139
- the Marketcircle server admin apps or the Billings Pro preferences.
140
-
141
- Asynchronous behaviour can often be worked around by using
142
- accessibility {file:docs/Notifications.markdown notifications} or by
143
- simply calling
144
- [`sleep`](http://rdoc.info/stdlib/core/1.9.2/Kernel#sleep-instance_method).
145
- Notifications have some interesting caveats that are covered in their
146
- own tutorial and so it is much easier to `sleep`.
147
-
148
- However, using `sleep` to solve the problem of asynchronous waiting is
149
- like using a steak knife for brain surgery. Unless you can control the
150
- environment when the script is running, you will need to sleep for
151
- longer periods of time than you really need to; even when you have
152
- quite a bit of control you might still have the occasional instance
153
- where a database fetch takes longer than expected. I would suggest you
154
- use notifications when you can, and sleep when you cannot.
155
-
156
- ## CGEvents Actions
157
-
158
- CGEvents based actions never directly use the accessibility APIs and
159
- are implemented at a different level of the GUI stack in OS X (I
160
- assume). An action that uses CGEvents will actually move the mouse
161
- cursor around and simulate mouse input at a relatively low level of
162
- abstraction. However, accessibility information is still used to find
163
- the point on the screen to move to and click. These types of actions
164
- are more realistic, more awesome looking, and also more difficult to
165
- write.
166
-
167
- The difficulty in writing the scripts comes from the fact that it does
168
- not directly communicate with the accessibility APIs. This implicitly
169
- means that all CGEvents actions are asynchronous, which is the only
170
- non-trivial complication with using CGEvents APIs.
171
-
172
- ### CGEvents Goes Where Accessibility Cannot
173
-
174
- The benefit of actually moving the mouse will often outweigh the
175
- downside here. Moving the mouse cursor and generating click events
176
- allows things like dragging and scrolling which cannot be done using
177
- the accessibility APIs.
178
-
179
- As mentioned earlier, CGEvents doesn't talk to applications in the
180
- same way that accessibility APIs do, which means that you can use
181
- CGEvents actions on elements that do not fully support
182
- accessibility. For instance, text might appear on the UI and have a
183
- `click`-able link but may not provide an equivalent action to clicking
184
- on the link; using CGEvents APIs you will only need to move to the
185
- position of the text and then generate a click event.
186
-
187
- ### Realism
188
-
189
- Since CGEvents actually manipulates the mouse cursor, any script that
190
- uses the APIs will be more realistic than their equivalent
191
- accessibility actions. This can make a difference if you are testing
192
- an app and you are expecting other side effects to occur. For instance,
193
- what if you implemented a sort of UI element that requires the mouse
194
- cursor to be near the element for an action to trigger, such as an
195
- expanding drawer or a folder in the finder. Depending on what you are
196
- using AXElements for, this may or may not be important to you.
197
-
198
- ### Underpinnings
199
-
200
- An important thing to note is that AXElements always works with
201
- flipped co-ordinates. The origin, `(0, 0)` is always located in the
202
- top left corner of the main display. Displays to the left have a
203
- negative `x` co-ordinate and displays above the main display will have
204
- negative `y` co-ordinates.
205
-
206
- Though the CGEvents APIs are exposed to AXElements through the
207
- {Accessibility::Language Language} module, there is another thin
208
- layer between AXElements and CGEvents that abstracts everything that
209
- AXElements uses.
210
-
211
- The between layer is the {Mouse} module, and it is responsible for
212
- generating events and animating the mouse movements on screen. The API
213
- that it makes available to AXElements is more fine grained than what
214
- AXElements makes available to you. There is a lot of room for the
215
- {Mouse} module to grow and add new features, some of which are noted
216
- as `@todo` items in the documentation. It may also be necessary in
217
- the future to make available from AXElements certain options that are
218
- normally hidden in {Mouse}.
219
-
220
- ### Where The Mouse Moves
221
-
222
- Consider the following:
223
-
224
- move_mouse_to app.main_window.close_button
225
-
226
- If you try out that code snippet then you will notice that the mouse
227
- cursor moves to the button, specifically the center of the button. If
228
- you use the method to move to another object, such as a button, you
229
- will again notice that it moved to the center point of the element.
230
-
231
- You can call `move_mouse_to` and give it a UI element, but you could
232
- also be more exact and give it a CGPoint or even an array with two
233
- numbers. In fact, you could pass just any object as long it implements
234
- a method named `to_point` which returns a CGPoint object. If you were
235
- to look at the code for {Accessibility::Language#move_mouse_to} you
236
- would see that all it does is call `#to_point` on the argument and
237
- then pass the returned value to the {Mouse} module to actually do the
238
- work. You could also look at {NSArray#to_point} and find out that it just
239
- returns a new CGPoint using the first two objects in the array, and
240
- `CGPoint#to_point` just returns itself (you can't see the
241
- documentation for the method because of limitations in
242
- YARD). Similarly, {AX::Element} implements
243
- {AX::Element#to_point to_point} which not only gets the position for
244
- the element, but also gets the size and then calculates the center
245
- point for the element. This is important because you probably never
246
- want the mouse cursor moving to the top left of the UI element, but
247
- maybe you don't want the cursor moving to the center either.
248
-
249
- If you want to have the mouse move to a location over a UI element
250
- that is not the center point, then you will need to implement
251
- `to_point` for the appropriate class. Just remember to follow the
252
- {file:docs/NewBehaviour.markdown rules} for choosing the proper
253
- superclasss. For instance, on Snow Leopard you could resize a window
254
- by clicking and dragging an `AX::GrowArea` object in the lower right
255
- corner of a window; moving to the center of the element may not
256
- actually allow the mouse to respond and so you would have to implement
257
- `to_point` to move closer to the bottom right of the window
258
- (__NOTE__: I don't know if that is actually true for `AX::GrowArea`,
259
- it was just meant as an example).
260
-
261
- ### Dragging
262
-
263
- Sometimes you just need to be able to click and drag an element. To do
264
- this you can only use the CGEvents APIs. Fortunately, click and drag
265
- is relatively painless with AXElements, the simplest example would be
266
- something like this:
267
-
268
- # move to the mouse to the starting point
269
- move_mouse_to app.main_window.title_ui_element
270
-
271
- # start the click and drag event
272
- drag_mouse_to [0, 0]
273
-
274
- Pretty cool, eh? The general pattern is to move the mouse to the
275
- starting point, and then to call `drag_mouse_to` to start the click
276
- and drag events. In the example, I gave co-ordinates using an array
277
- with two numbers but you can pass a CGPoint or anything else that
278
- responds to `to_point` just like with the other CGEvents actions.
279
-
280
- ## Keyboard Actions
281
-
282
- The final type of action is the keyboard action. Keyboard actions
283
- live in a world between the accessibility APIs and CGEvent APIs; that
284
- is, Apple has already done the work of unifying them for me and in a
285
- way that I would not have been able to do myself.
286
-
287
- The keyboard action is a single method,
288
- {Accessibility::Language#type}, that requires one parameter and
289
- takes an optional second parameter. The first parameter is simply a
290
- string that includes which characters you wish to have typed out and
291
- the second parameter is the UI element for the application where you
292
- would like to send the keyboard events. Since the second parameter is
293
- optional, the currently focused app will receive the events if the
294
- parameter is not used.
295
-
296
- # to a specific app
297
- app = Accessibility.application_with_name 'Terminal'
298
- type "Hello, world!", app
299
-
300
- # to the focused app
301
- type "Hello, world!"
302
-
303
- The string that you pass can have some special escape sequences to
304
- indicate that you want to press command characters or a combination of
305
- keys at once for a hot key. Details on this are contained in their own
306
- tutorial, the
307
- {file:docs/KeyboardEvents.markdown Keyboard Events Tutorial}.
308
-
309
- ## Mixing In
310
-
311
- By now you will have noticed that all the actions have been defined in
312
- the {Accessibility::Language} name space, which is just a simple
313
- module. Though, you have been able to use the methods anywhere and in
314
- any context.
315
-
316
- When you load AXElements, by using `require 'ax_elements'`, not only
317
- will all the code be loaded, but the extra step of mixing
318
- {Accessibility::Language} into the top level name space will also be
319
- done for you. The only way to override this behaviour is to load the
320
- components of AXElements yourself, the details on how to do this are
321
- left as an exercise to interested parties. :P
322
-
323
- ## A Note About Caching
324
-
325
- You need to be careful when you cache UI element objects. Every time
326
- that you trigger an action you are intrinsically changing the state of
327
- an application. State changes will often cause new UI elements to be
328
- created, recreated, or removed.
329
-
330
- For example consider pressing the close button for a window; in this
331
- case, an entire window and all of its children will disappear and
332
- become invalid objects. Another case might be pressing the `+` button
333
- for a table; in this case you have created a new row for the table and
334
- any cache of the existing rows for the table will not include the new
335
- element.
336
-
337
- The real problem with caching is with the invalid objects. An invalid
338
- object is poisonous to the MacRuby run time. If you try to access an
339
- attribute or trigger a search from an invalid then you will cause
340
- MacRuby to crash.