AXElements 0.9.0 → 1.0.0.alpha

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 (60) hide show
  1. data/.yardopts +0 -4
  2. data/README.markdown +22 -17
  3. data/Rakefile +1 -1
  4. data/ext/accessibility/key_coder/extconf.rb +1 -1
  5. data/ext/accessibility/key_coder/key_coder.c +2 -4
  6. data/lib/accessibility.rb +3 -3
  7. data/lib/accessibility/core.rb +948 -0
  8. data/lib/accessibility/dsl.rb +30 -186
  9. data/lib/accessibility/enumerators.rb +1 -0
  10. data/lib/accessibility/factory.rb +78 -134
  11. data/lib/accessibility/graph.rb +5 -9
  12. data/lib/accessibility/highlighter.rb +86 -0
  13. data/lib/accessibility/{pretty_printer.rb → pp_inspector.rb} +4 -3
  14. data/lib/accessibility/qualifier.rb +3 -5
  15. data/lib/accessibility/screen_recorder.rb +217 -0
  16. data/lib/accessibility/statistics.rb +57 -0
  17. data/lib/accessibility/translator.rb +23 -32
  18. data/lib/accessibility/version.rb +2 -22
  19. data/lib/ax/application.rb +20 -159
  20. data/lib/ax/element.rb +42 -32
  21. data/lib/ax/scroll_area.rb +5 -6
  22. data/lib/ax/systemwide.rb +1 -33
  23. data/lib/ax_elements.rb +1 -9
  24. data/lib/ax_elements/core_graphics_workaround.rb +5 -0
  25. data/lib/ax_elements/nsarray_compat.rb +17 -97
  26. data/lib/ax_elements/vendor/inflection_data.rb +66 -0
  27. data/lib/ax_elements/vendor/inflections.rb +176 -0
  28. data/lib/ax_elements/vendor/inflector.rb +306 -0
  29. data/lib/minitest/ax_elements.rb +180 -0
  30. data/lib/mouse.rb +227 -0
  31. data/lib/rspec/expectations/ax_elements.rb +234 -0
  32. data/rakelib/gem.rake +3 -12
  33. data/rakelib/test.rake +15 -0
  34. data/test/helper.rb +20 -10
  35. data/test/integration/accessibility/test_core.rb +18 -0
  36. data/test/integration/accessibility/test_dsl.rb +40 -38
  37. data/test/integration/accessibility/test_enumerators.rb +1 -0
  38. data/test/integration/accessibility/test_graph.rb +0 -1
  39. data/test/integration/accessibility/test_qualifier.rb +2 -2
  40. data/test/integration/ax/test_application.rb +2 -9
  41. data/test/integration/ax/test_element.rb +0 -40
  42. data/test/integration/minitest/test_ax_elements.rb +89 -0
  43. data/test/integration/rspec/expectations/test_ax_elements.rb +102 -0
  44. data/test/sanity/accessibility/test_factory.rb +2 -2
  45. data/test/sanity/accessibility/test_highlighter.rb +56 -0
  46. data/test/sanity/accessibility/{test_pretty_printer.rb → test_pp_inspector.rb} +9 -9
  47. data/test/sanity/accessibility/test_statistics.rb +57 -0
  48. data/test/sanity/ax/test_application.rb +1 -16
  49. data/test/sanity/ax/test_element.rb +2 -2
  50. data/test/sanity/ax_elements/test_nsobject_inspect.rb +2 -4
  51. data/test/sanity/minitest/test_ax_elements.rb +17 -0
  52. data/test/sanity/rspec/expectations/test_ax_elements.rb +15 -0
  53. data/test/sanity/test_mouse.rb +22 -0
  54. data/test/test_core.rb +454 -0
  55. metadata +44 -69
  56. data/History.markdown +0 -41
  57. data/lib/accessibility/system_info.rb +0 -230
  58. data/lib/ax_elements/active_support_selections.rb +0 -10
  59. data/lib/ax_elements/mri.rb +0 -57
  60. data/test/sanity/accessibility/test_version.rb +0 -15
@@ -1,15 +1,17 @@
1
1
  require 'accessibility/version'
2
- require 'active_support/inflector'
3
- require 'ax_elements/mri'
4
-
5
- ActiveSupport::Inflector.inflections do |inflect|
6
- # Related to accessibility
7
- inflect.acronym('UI')
8
- inflect.acronym('RTF')
9
- inflect.acronym('URL')
10
- end
2
+ require 'ax_elements/vendor/inflector'
3
+
4
+ framework 'ApplicationServices'
11
5
 
12
- framework 'ApplicationServices' if on_macruby?
6
+ unless Object.const_defined? :KAXIdentifierAttribute
7
+ ##
8
+ # Added for backwards compatability with Snow Leopard.
9
+ # This attribute is standard with Lion and newer. AXElements depends
10
+ # on it being defined.
11
+ #
12
+ # @return [String]
13
+ KAXIdentifierAttribute = 'AXIdentifier'
14
+ end
13
15
 
14
16
 
15
17
  ##
@@ -70,9 +72,7 @@ class Accessibility::Translator
70
72
  # @param keys [Array<String>]
71
73
  # @return [Array<Symbol>]
72
74
  def rubyize keys
73
- keys = keys.map { |x| @rubyisms[x] }
74
- keys.flatten!
75
- keys
75
+ keys.map { |x| @rubyisms[x] }
76
76
  end
77
77
 
78
78
  ##
@@ -133,17 +133,6 @@ class Accessibility::Translator
133
133
 
134
134
  private
135
135
 
136
- def preloads
137
- {
138
- # basic preloads
139
- id: KAXIdentifierAttribute,
140
- placeholder: KAXPlaceholderValueAttribute,
141
- # workarounds for known case where AX uses "Is" for a boolean attribute
142
- application_running: KAXIsApplicationRunningAttribute,
143
- application_running?: KAXIsApplicationRunningAttribute,
144
- }
145
- end
146
-
147
136
  # @return [Hash{String=>String}]
148
137
  def init_unprefixes
149
138
  @unprefixes = Hash.new do |hash, key|
@@ -154,32 +143,34 @@ class Accessibility::Translator
154
143
  # @return [Hash{String=>Symbol}]
155
144
  def init_rubyisms
156
145
  @rubyisms = Hash.new do |hash, key|
157
- hash[key] = [ActiveSupport::Inflector.underscore(@unprefixes[key]).to_sym]
146
+ hash[key] = Accessibility::Inflector.underscore(@unprefixes[key]).to_sym
158
147
  end
159
- preloads.each_pair do |k,v| @rubyisms[v] << k end
160
148
  end
161
149
 
162
150
  # @return [Hash{Symbol=>String}]
163
151
  def init_cocoaifications
164
152
  @cocoaifications = Hash.new do |hash, key|
165
- str_key = key.to_s
166
- str_key.chomp! QUESTION_MARK
167
- hash[key] = "AX#{ActiveSupport::Inflector.camelize(str_key)}"
153
+ hash[key] = "AX#{Accessibility::Inflector.camelize(key.chomp QUESTION_MARK)}"
168
154
  end
169
- preloads.each_pair do |k, v| @cocoaifications[k] = v end
155
+ # preload the table
156
+ @cocoaifications[:id] = KAXIdentifierAttribute
157
+ @cocoaifications[:placeholder] = KAXPlaceholderValueAttribute
158
+ # workaround the one known case where AX uses "Is" for a boolean attribute
159
+ @cocoaifications[:application_running] = # let the value all fall through
160
+ @cocoaifications[:application_running?] = KAXIsApplicationRunningAttribute
170
161
  end
171
162
 
172
163
  # @return [Hash{String=>String}]
173
164
  def init_classifications
174
165
  @classifications = Hash.new do |hash, key|
175
- hash[key] = ActiveSupport::Inflector.classify(key)
166
+ hash[key] = Accessibility::Inflector.classify(key)
176
167
  end
177
168
  end
178
169
 
179
170
  # @return [Hash{String=>String}]
180
171
  def init_singularizations
181
172
  @singularizations = Hash.new do |hash, key|
182
- hash[key] = ActiveSupport::Inflector.singularize(key)
173
+ hash[key] = Accessibility::Inflector.singularize(key)
183
174
  end
184
175
  end
185
176
 
@@ -4,28 +4,8 @@
4
4
  # The main AXElements namespace.
5
5
  module Accessibility
6
6
  # @return [String]
7
- VERSION = '0.9.0'
7
+ VERSION = '1.0.0.alpha'
8
8
 
9
9
  # @return [String]
10
- CODE_NAME = 'エネコロロ'
11
-
12
- # @return [String]
13
- ENGINE = case RUBY_ENGINE
14
- when 'macruby' then 'サンダース'
15
- when 'ruby' then 'ブースター'
16
- when 'rbx' then 'ブラッキー' # for when rbx has good cext support
17
- else 'シャワーズ' # vapor(ware)eon
18
- end
19
-
20
- ##
21
- # The complete version string for AXElements
22
- #
23
- # This differs from {Accessibility::VERSION} in that it also
24
- # includes `RUBY_ENGINE` information.
25
- #
26
- # @return [String]
27
- def self.version
28
- "#{VERSION}-#{ENGINE}"
29
- end
30
-
10
+ CODE_NAME = 'ルナトーン'
31
11
  end
@@ -11,102 +11,12 @@ require 'accessibility/string'
11
11
  class AX::Application < AX::Element
12
12
  include Accessibility::String
13
13
 
14
- class << self
15
- ##
16
- # Asynchronously launch an application with given the bundle identifier
17
- #
18
- # @param bundle [String] bundle identifier for the app
19
- # @return [Boolean]
20
- def launch bundle
21
- NSWorkspace.sharedWorkspace.launchAppWithBundleIdentifier bundle,
22
- options: NSWorkspace::NSWorkspaceLaunchAsync,
23
- additionalEventParamDescriptor: nil,
24
- launchIdentifier: nil
25
- end
26
-
27
- ##
28
- # Find and return the dock application
29
- #
30
- # @return [AX::Application]
31
- def dock
32
- new 'com.apple.dock'
33
- end
34
-
35
- ##
36
- # Find and return the dock application
37
- #
38
- # @return [AX::Application]
39
- def finder
40
- new 'com.apple.finder'
41
- end
42
-
43
- ##
44
- # Find and return the notification center UI app
45
- #
46
- # Obviously, this will only work on OS X 10.8+
47
- #
48
- # @return [AX::Application]
49
- def notification_center
50
- new 'com.apple.notificationcenterui'
51
- end
52
-
53
- ##
54
- # Find and return the application which is frontmost
55
- #
56
- # This is often, but not necessarily, the same as the app that
57
- # owns the menu bar.
58
- #
59
- # @return [AX::Application]
60
- def frontmost_application
61
- new NSWorkspace.sharedWorkspace.frontmostApplication
62
- end
63
- alias_method :frontmost_app, :frontmost_application
64
-
65
- ##
66
- # Find and return the application which owns the menu bar
67
- #
68
- # This is often, but not necessarily, the same as the app that
69
- # is frontmost.
70
- #
71
- # @return [AX::Application]
72
- def menu_bar_owner
73
- new NSWorkspace.sharedWorkspace.menuBarOwningApplication
74
- end
75
- end
76
-
77
14
  ##
78
- # Standard way of creating a new application object
79
- #
80
15
  # You can initialize an application object with either the process
81
16
  # identifier (pid) of the application, the name of the application,
82
17
  # an `NSRunningApplication` instance for the application, or an
83
18
  # accessibility (`AXUIElementRef`) token.
84
19
  #
85
- # Given a PID, we try to lookup the application and wrap it.
86
- #
87
- # Given an `NSRunningApplication` instance, we simply wrap it.
88
- #
89
- # Given a string we do some complicated magic to try and figure out if
90
- # the string is a bundle identifier or the localized name of the
91
- # application. Given a bundle identifier we try to launch the app if
92
- # it is not already running, given a localized name we search the running
93
- # applications for the app. We wrap what we get back if we get anything
94
- # back.
95
- #
96
- # Note however, given a bundle identifier to launch the application our
97
- # implementation is a bit of a hack; I've tried to register for
98
- # notifications, launch synchronously, etc., but there is always a problem
99
- # with accessibility not being ready right away, so we will poll the app
100
- # to see when it is ready with a timeout of ~10 seconds.
101
- #
102
- # If this method fails to find an app then an exception will be raised.
103
- #
104
- # @example
105
- #
106
- # AX::Application.new 'com.apple.mail'
107
- # AX::Application.new 'Mail'
108
- # AX::Application.new 43567
109
- #
110
20
  # @param arg [Number,String,NSRunningApplication]
111
21
  def initialize arg
112
22
  case arg
@@ -114,29 +24,14 @@ class AX::Application < AX::Element
114
24
  super Accessibility::Element.application_for arg
115
25
  @app = NSRunningApplication.runningApplicationWithProcessIdentifier arg
116
26
  when String
117
- until @app
118
- @app =
119
- (
120
- app = NSRunningApplication.runningApplicationsWithBundleIdentifier arg
121
- app.first
122
-
123
- ) || (
124
- spin
125
- NSWorkspace.sharedWorkspace.runningApplications.find { |app|
126
- app.localizedName == arg
127
- }
128
-
129
- ) || (
130
- count ||= 0
131
- if AX::Application.launch arg
132
- spin 1
133
- count += 1
134
- raise "#{arg} failed to launch in time" if count == 10
135
- else
136
- raise "#{arg} is not a registered bundle identifier for the system"
137
- end
27
+ @app =
28
+ NSRunningApplication.runningApplicationsWithBundleIdentifier(arg).first ||
29
+ (
30
+ spin_run_loop
31
+ NSWorkspace.sharedWorkspace.runningApplications.find { |app|
32
+ app.localizedName == arg
33
+ }
138
34
  )
139
- end
140
35
  super Accessibility::Element.application_for @app.processIdentifier
141
36
  when NSRunningApplication
142
37
  super Accessibility::Element.application_for arg.processIdentifier
@@ -172,7 +67,7 @@ class AX::Application < AX::Element
172
67
  # to the dynamic `#focused?` method, but might make more sense to use
173
68
  # in some cases.
174
69
  def active?
175
- spin
70
+ spin_run_loop
176
71
  @app.active?
177
72
  end
178
73
  alias_method :focused, :active?
@@ -181,14 +76,14 @@ class AX::Application < AX::Element
181
76
  ##
182
77
  # Ask the app whether or not it is hidden.
183
78
  def hidden?
184
- spin
79
+ spin_run_loop
185
80
  @app.hidden?
186
81
  end
187
82
 
188
83
  ##
189
84
  # Ask the app whether or not it is still running.
190
85
  def terminated?
191
- spin
86
+ spin_run_loop
192
87
  @app.terminated?
193
88
  end
194
89
 
@@ -247,62 +142,23 @@ class AX::Application < AX::Element
247
142
 
248
143
  # @group Actions
249
144
 
250
- ##
251
- # @note This is often async and may return before the action is completed
252
- #
253
- # Ask the app to quit
254
- #
255
- # @return [Boolean]
256
- def terminate
257
- perform :terminate
258
- end
259
-
260
- ##
261
- # @note This is often async and may return before the action is completed
262
- #
263
- # Force the app to quit
264
- #
265
- # @return [Boolean]
266
- def terminate!
267
- perform :force_terminate
268
- end
269
-
270
- ##
271
- # @note This is often async and may return before the action is completed
272
- #
273
- # Ask the app to hide itself
274
- #
275
- # @return [Boolean]
276
- def hide
277
- perform :hide
278
- end
279
-
280
- ##
281
- # @note This is often async and may return before the action is completed
282
- #
283
- # As the app to unhide itself and bring to front
284
- #
285
- # @return [Boolean]
286
- def unhide
287
- perform :unhide
288
- end
289
145
 
290
146
  # (see AX::Element#perform)
291
147
  def perform name
292
148
  case name
293
149
  when :terminate
294
150
  return true if terminated?
295
- @app.terminate; spin 0.25; terminated?
151
+ @app.terminate; sleep 0.2; terminated?
296
152
  when :force_terminate
297
153
  return true if terminated?
298
- @app.forceTerminate; spin 0.25; terminated?
154
+ @app.forceTerminate; sleep 0.2; terminated?
299
155
  when :hide
300
156
  return true if hidden?
301
- @app.hide; spin 0.25; hidden?
157
+ @app.hide; sleep 0.2; hidden?
302
158
  when :unhide
303
159
  return true if active?
304
- @app.activateWithOptions(NSRunningApplication::NSApplicationActivateIgnoringOtherApps)
305
- spin 0.25; active?
160
+ @app.activateWithOptions(NSApplicationActivateIgnoringOtherApps)
161
+ sleep 0.2; active?
306
162
  else
307
163
  super
308
164
  end
@@ -432,6 +288,11 @@ class AX::Application < AX::Element
432
288
 
433
289
  private
434
290
 
291
+ # @return [nil]
292
+ def spin_run_loop
293
+ NSRunLoop.currentRunLoop.runUntilDate Time.now
294
+ end
295
+
435
296
  # @return [NSBundle]
436
297
  def bundle
437
298
  @bundle ||= NSBundle.bundleWithURL @app.bundleURL
data/lib/ax/element.rb CHANGED
@@ -1,13 +1,12 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- require 'active_support/core_ext/object/blank'
4
3
  require 'accessibility/core'
5
4
  require 'accessibility/factory'
6
5
  require 'accessibility/translator'
7
6
  require 'accessibility/enumerators'
8
7
  require 'accessibility/qualifier'
9
8
  require 'accessibility/errors'
10
- require 'accessibility/pretty_printer'
9
+ require 'accessibility/pp_inspector'
11
10
 
12
11
 
13
12
  ##
@@ -20,7 +19,7 @@ require 'accessibility/pretty_printer'
20
19
  # This abstract base class provides generic functionality that all
21
20
  # accessibility objects require.
22
21
  class AX::Element
23
- include Accessibility::PrettyPrinter
22
+ include Accessibility::PPInspector
24
23
 
25
24
  # @param ref [AXUIElementRef]
26
25
  def initialize ref
@@ -55,7 +54,7 @@ class AX::Element
55
54
  #
56
55
  # @param attr [#to_sym]
57
56
  def attribute attr
58
- @ref.attribute(TRANSLATOR.cocoaify(attr)).to_ruby
57
+ @ref.attribute TRANSLATOR.cocoaify(attr)
59
58
  end
60
59
 
61
60
  ##
@@ -66,7 +65,7 @@ class AX::Element
66
65
  #
67
66
  # @return [String]
68
67
  def description
69
- attribute(:description).to_ruby
68
+ attribute :description
70
69
  end
71
70
 
72
71
  ##
@@ -74,12 +73,12 @@ class AX::Element
74
73
  #
75
74
  # @return [Array<AX::Element>]
76
75
  def children
77
- @ref.children.to_ruby
76
+ attribute :children
78
77
  end
79
78
 
80
79
  ##
81
80
  # Get a list of elements, starting with the receiver and riding
82
- # the hierarchy up to the top level object (i.e. the {AX::Application})
81
+ # the hierarchy up to the top level object (i.e. the {AX::Application}).
83
82
  #
84
83
  # @example
85
84
  #
@@ -88,16 +87,15 @@ class AX::Element
88
87
  # # => [#<AX::ApplicationDockItem...>, #<AX::List...>, #<AX::Application...>]
89
88
  #
90
89
  # @return [Array<AX::Element>]
91
- def ancestry elements = self
92
- elements = Array(elements)
90
+ def ancestry *elements
91
+ elements = [self] if elements.empty?
93
92
  element = elements.last
94
93
  if element.attributes.include? :parent
95
- ancestry(elements << element.attribute(:parent))
94
+ ancestry(elements << element.parent)
96
95
  else
97
96
  elements
98
97
  end
99
98
  end
100
- alias_method :lineage, :ancestry
101
99
 
102
100
  ##
103
101
  # Get the process identifier for the application that the element
@@ -187,8 +185,8 @@ class AX::Element
187
185
  # @param attr [#to_sym]
188
186
  # @param param [Object]
189
187
  def parameterized_attribute attr, param
190
- param = param.relative_to(@ref.value.size) if param.kind_of? Range
191
- @ref.parameterized_attribute(TRANSLATOR.cocoaify(attr), param).to_ruby
188
+ param = param.relative_to(@ref.value.size) if value.kind_of? Range
189
+ @ref.parameterized_attribute TRANSLATOR.cocoaify(attr), param
192
190
  end
193
191
 
194
192
 
@@ -264,8 +262,6 @@ class AX::Element
264
262
  # As the opposite of {#search}, this also takes filters, and can
265
263
  # be used to find a specific ancestor for the current element.
266
264
  #
267
- # Returns `nil` if no ancestor is found.
268
- #
269
265
  # @example
270
266
  #
271
267
  # button.ancestor :window # => #<AX::StandardWindow>
@@ -274,13 +270,12 @@ class AX::Element
274
270
  # @param kind [#to_s]
275
271
  # @param filters [Hash{Symbol=>Object}]
276
272
  # @yield Optional block used for search filtering
277
- # @return [AX::Element,nil]
273
+ # @return [AX::Element]
278
274
  def ancestor kind, filters = {}, &block
279
275
  qualifier = Accessibility::Qualifier.new(kind, filters, &block)
280
- element = self
276
+ element = attribute :parent
281
277
  until qualifier.qualifies? element
282
278
  element = element.attribute :parent
283
- return nil unless element
284
279
  end
285
280
  element
286
281
  end
@@ -342,7 +337,7 @@ class AX::Element
342
337
  return attribute(method)
343
338
 
344
339
  elsif @ref.parameterized_attributes.include? key
345
- return parameterized_attribute(method, args.first)
340
+ return paramaterized_attribute(method, args.first)
346
341
 
347
342
  elsif @ref.attributes.include? KAXChildrenAttribute
348
343
  if (result = search(method, *args, &block)).blank?
@@ -365,7 +360,7 @@ class AX::Element
365
360
  #
366
361
  # @return [String]
367
362
  def inspect
368
- msg = "#<#{self.class}" << pp_identifier.to_s
363
+ msg = "#<#{self.class}" << pp_identifier
369
364
  msg << pp_position if attributes.include? :position
370
365
  msg << pp_children if attributes.include? :children
371
366
  msg << pp_checkbox(:enabled) if attributes.include? :enabled
@@ -435,7 +430,7 @@ class AX::Element
435
430
  def bounds
436
431
  CGRect.new(attribute(:position), attribute(:size))
437
432
  end
438
- alias_method :to_rect, :bounds
433
+ alias_method :rect, :bounds
439
434
 
440
435
  ##
441
436
  # Get the application object for the element.
@@ -461,17 +456,11 @@ class AX::Element
461
456
  @ref.invalid?
462
457
  end
463
458
 
464
- if on_macruby?
465
- ##
466
- # Like {#respond_to?}, this is overriden to include attribute methods.
467
- # Though, it does include dynamic predicate methods at the moment.
468
- def methods include_super = true, include_objc_super = false
469
- super.concat(attributes).concat(parameterized_attributes)
470
- end
471
- else
472
- def methods include_super = true
473
- super.concat(attributes).concat(parameterized_attributes)
474
- end
459
+ ##
460
+ # Like {#respond_to?}, this is overriden to include attribute methods.
461
+ # Though, it does include dynamic predicate methods at the moment.
462
+ def methods include_super = true, include_objc_super = false
463
+ super.concat(attributes).concat(parameterized_attributes)
475
464
  end
476
465
 
477
466
  ##
@@ -496,3 +485,24 @@ class AX::Element
496
485
  TRANSLATOR = Accessibility::Translator.instance
497
486
 
498
487
  end
488
+
489
+
490
+ # Extensions so checking `#blank?` on search result "just works".
491
+ class NSArray
492
+ # (see NilClass#blank?)
493
+ alias_method :blank?, :empty?
494
+ end
495
+
496
+ # Extensions so checking `#blank?` on search result "just works".
497
+ class NilClass
498
+ ##
499
+ # Whether or not the object is "blank". The concept of blankness
500
+ # borrowed from `Active Support` and is true if the object is falsey
501
+ # or `#empty?`.
502
+ #
503
+ # This method is used by implicit searching in AXElements to
504
+ # determine if searches yielded responses.
505
+ def blank?
506
+ true
507
+ end
508
+ end